diff --git a/Cargo.lock b/Cargo.lock index 1896ef6ca445..d4391841adb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -443,7 +443,7 @@ dependencies = [ [[package]] name = "beefy-gadget" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "array-bytes", "async-trait", @@ -477,7 +477,7 @@ dependencies = [ [[package]] name = "beefy-gadget-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "beefy-gadget", "futures", @@ -496,7 +496,7 @@ dependencies = [ [[package]] name = "beefy-merkle-tree" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "sp-api", "sp-beefy", @@ -1983,9 +1983,9 @@ dependencies = [ [[package]] name = "environmental" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b91989ae21441195d7d9b9993a2f9295c7e1a8c96255d8b729accddc124797" +checksum = "e48c92028aaa870e83d51c64e5d4e0b6981b360c522198c23959f219a4e1b15b" [[package]] name = "erased-serde" @@ -2178,9 +2178,9 @@ dependencies = [ [[package]] name = "finality-grandpa" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b22349c6a11563a202d95772a68e0fcf56119e74ea8a2a19cf2301460fcd0df5" +checksum = "e24e6c429951433ccb7c87fd528c60084834dcd14763182c1f83291bcde24c34" dependencies = [ "either", "futures", @@ -2268,7 +2268,7 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "fork-tree" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "parity-scale-codec", ] @@ -2292,7 +2292,7 @@ checksum = "85dcb89d2b10c5f6133de2efd8c11959ce9dbb46a2f7a4cab208c4eeda6ce1ab" [[package]] name = "frame-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-support", "frame-system", @@ -2315,7 +2315,7 @@ dependencies = [ [[package]] name = "frame-benchmarking-cli" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "Inflector", "array-bytes", @@ -2362,7 +2362,7 @@ dependencies = [ [[package]] name = "frame-election-provider-solution-type" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2373,7 +2373,7 @@ dependencies = [ [[package]] name = "frame-election-provider-support" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-election-provider-solution-type", "frame-support", @@ -2390,7 +2390,7 @@ dependencies = [ [[package]] name = "frame-executive" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-support", "frame-system", @@ -2419,7 +2419,7 @@ dependencies = [ [[package]] name = "frame-remote-externalities" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "futures", "log", @@ -2435,7 +2435,7 @@ dependencies = [ [[package]] name = "frame-support" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "bitflags", "frame-metadata", @@ -2467,7 +2467,7 @@ dependencies = [ [[package]] name = "frame-support-procedural" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "Inflector", "cfg-expr", @@ -2481,7 +2481,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-support-procedural-tools-derive", "proc-macro-crate", @@ -2493,7 +2493,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools-derive" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "proc-macro2", "quote", @@ -2503,7 +2503,7 @@ dependencies = [ [[package]] name = "frame-support-test" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-support", "frame-support-test-pallet", @@ -2526,7 +2526,7 @@ dependencies = [ [[package]] name = "frame-support-test-pallet" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-support", "frame-system", @@ -2537,7 +2537,7 @@ dependencies = [ [[package]] name = "frame-system" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-support", "log", @@ -2555,7 +2555,7 @@ dependencies = [ [[package]] name = "frame-system-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -2570,7 +2570,7 @@ dependencies = [ [[package]] name = "frame-system-rpc-runtime-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "parity-scale-codec", "sp-api", @@ -2579,7 +2579,7 @@ dependencies = [ [[package]] name = "frame-try-runtime" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-support", "parity-scale-codec", @@ -2750,7 +2750,7 @@ dependencies = [ [[package]] name = "generate-bags" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "chrono", "frame-election-provider-support", @@ -2851,9 +2851,9 @@ checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793" [[package]] name = "git2" -version = "0.14.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3826a6e0e2215d7a41c2bfc7c9244123969273f3476b939a226aac0ab56e9e3c" +checksum = "be36bc9e0546df253c0cc41fd0af34f5e92845ad8509462ec76672fac6997f5b" dependencies = [ "bitflags", "libc", @@ -3690,9 +3690,9 @@ checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] name = "libgit2-sys" -version = "0.13.2+1.4.2" +version = "0.14.1+1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b" +checksum = "4a07fb2692bc3593bda59de45a502bb3071659f2c515e28c71e728306b038e17" dependencies = [ "cc", "libc", @@ -4476,7 +4476,7 @@ dependencies = [ [[package]] name = "mmr-gadget" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "futures", "log", @@ -4495,7 +4495,7 @@ dependencies = [ [[package]] name = "mmr-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "anyhow", "jsonrpsee", @@ -5046,7 +5046,7 @@ dependencies = [ [[package]] name = "pallet-assets" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5061,7 +5061,7 @@ dependencies = [ [[package]] name = "pallet-authority-discovery" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-support", "frame-system", @@ -5077,7 +5077,7 @@ dependencies = [ [[package]] name = "pallet-authorship" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-support", "frame-system", @@ -5092,7 +5092,7 @@ dependencies = [ [[package]] name = "pallet-babe" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5116,7 +5116,7 @@ dependencies = [ [[package]] name = "pallet-bags-list" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -5136,7 +5136,7 @@ dependencies = [ [[package]] name = "pallet-bags-list-remote-tests" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-election-provider-support", "frame-remote-externalities", @@ -5155,7 +5155,7 @@ dependencies = [ [[package]] name = "pallet-balances" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5170,7 +5170,7 @@ dependencies = [ [[package]] name = "pallet-beefy" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-support", "frame-system", @@ -5186,7 +5186,7 @@ dependencies = [ [[package]] name = "pallet-beefy-mmr" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "array-bytes", "beefy-merkle-tree", @@ -5209,7 +5209,7 @@ dependencies = [ [[package]] name = "pallet-bounties" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5227,7 +5227,7 @@ dependencies = [ [[package]] name = "pallet-child-bounties" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5246,7 +5246,7 @@ dependencies = [ [[package]] name = "pallet-collective" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5263,7 +5263,7 @@ dependencies = [ [[package]] name = "pallet-conviction-voting" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "assert_matches", "frame-benchmarking", @@ -5280,7 +5280,7 @@ dependencies = [ [[package]] name = "pallet-democracy" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5298,7 +5298,7 @@ dependencies = [ [[package]] name = "pallet-election-provider-multi-phase" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -5321,7 +5321,7 @@ dependencies = [ [[package]] name = "pallet-election-provider-support-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -5334,7 +5334,7 @@ dependencies = [ [[package]] name = "pallet-elections-phragmen" version = "5.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5352,7 +5352,7 @@ dependencies = [ [[package]] name = "pallet-fast-unstake" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -5370,7 +5370,7 @@ dependencies = [ [[package]] name = "pallet-grandpa" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5393,7 +5393,7 @@ dependencies = [ [[package]] name = "pallet-identity" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "enumflags2", "frame-benchmarking", @@ -5409,7 +5409,7 @@ dependencies = [ [[package]] name = "pallet-im-online" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5429,7 +5429,7 @@ dependencies = [ [[package]] name = "pallet-indices" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5446,7 +5446,7 @@ dependencies = [ [[package]] name = "pallet-membership" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5463,7 +5463,7 @@ dependencies = [ [[package]] name = "pallet-mmr" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5480,7 +5480,7 @@ dependencies = [ [[package]] name = "pallet-multisig" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5496,7 +5496,7 @@ dependencies = [ [[package]] name = "pallet-nis" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5512,7 +5512,7 @@ dependencies = [ [[package]] name = "pallet-nomination-pools" version = "1.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-support", "frame-system", @@ -5529,7 +5529,7 @@ dependencies = [ [[package]] name = "pallet-nomination-pools-benchmarking" version = "1.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -5549,7 +5549,7 @@ dependencies = [ [[package]] name = "pallet-nomination-pools-runtime-api" version = "1.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "parity-scale-codec", "sp-api", @@ -5559,7 +5559,7 @@ dependencies = [ [[package]] name = "pallet-offences" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-support", "frame-system", @@ -5576,12 +5576,13 @@ dependencies = [ [[package]] name = "pallet-offences-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-election-provider-support", "frame-support", "frame-system", + "log", "pallet-babe", "pallet-balances", "pallet-grandpa", @@ -5599,7 +5600,7 @@ dependencies = [ [[package]] name = "pallet-preimage" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5616,7 +5617,7 @@ dependencies = [ [[package]] name = "pallet-proxy" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5631,7 +5632,7 @@ dependencies = [ [[package]] name = "pallet-ranked-collective" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5649,7 +5650,7 @@ dependencies = [ [[package]] name = "pallet-recovery" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5664,7 +5665,7 @@ dependencies = [ [[package]] name = "pallet-referenda" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "assert_matches", "frame-benchmarking", @@ -5683,7 +5684,7 @@ dependencies = [ [[package]] name = "pallet-scheduler" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5700,7 +5701,7 @@ dependencies = [ [[package]] name = "pallet-session" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-support", "frame-system", @@ -5721,7 +5722,7 @@ dependencies = [ [[package]] name = "pallet-session-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5737,7 +5738,7 @@ dependencies = [ [[package]] name = "pallet-society" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-support", "frame-system", @@ -5751,7 +5752,7 @@ dependencies = [ [[package]] name = "pallet-staking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -5774,7 +5775,7 @@ dependencies = [ [[package]] name = "pallet-staking-reward-curve" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -5785,7 +5786,7 @@ dependencies = [ [[package]] name = "pallet-staking-reward-fn" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "log", "sp-arithmetic", @@ -5794,7 +5795,7 @@ dependencies = [ [[package]] name = "pallet-state-trie-migration" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5811,7 +5812,7 @@ dependencies = [ [[package]] name = "pallet-sudo" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-support", "frame-system", @@ -5825,7 +5826,7 @@ dependencies = [ [[package]] name = "pallet-timestamp" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5843,7 +5844,7 @@ dependencies = [ [[package]] name = "pallet-tips" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5862,7 +5863,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-support", "frame-system", @@ -5878,7 +5879,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "jsonrpsee", "pallet-transaction-payment-rpc-runtime-api", @@ -5894,7 +5895,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc-runtime-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "pallet-transaction-payment", "parity-scale-codec", @@ -5906,7 +5907,7 @@ dependencies = [ [[package]] name = "pallet-treasury" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5920,10 +5921,25 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-uniques" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-utility" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5939,7 +5955,7 @@ dependencies = [ [[package]] name = "pallet-vesting" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5954,7 +5970,7 @@ dependencies = [ [[package]] name = "pallet-whitelist" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5970,6 +5986,7 @@ dependencies = [ name = "pallet-xcm" version = "0.9.33" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "log", @@ -8844,7 +8861,7 @@ dependencies = [ [[package]] name = "sc-allocator" version = "4.1.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "log", "sp-core", @@ -8855,7 +8872,7 @@ dependencies = [ [[package]] name = "sc-authority-discovery" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "async-trait", "futures", @@ -8882,7 +8899,7 @@ dependencies = [ [[package]] name = "sc-basic-authorship" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "futures", "futures-timer", @@ -8905,7 +8922,7 @@ dependencies = [ [[package]] name = "sc-block-builder" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "parity-scale-codec", "sc-client-api", @@ -8921,7 +8938,7 @@ dependencies = [ [[package]] name = "sc-chain-spec" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "memmap2", "sc-chain-spec-derive", @@ -8936,7 +8953,7 @@ dependencies = [ [[package]] name = "sc-chain-spec-derive" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -8947,7 +8964,7 @@ dependencies = [ [[package]] name = "sc-cli" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "array-bytes", "chrono", @@ -8987,7 +9004,7 @@ dependencies = [ [[package]] name = "sc-client-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "fnv", "futures", @@ -9013,7 +9030,7 @@ dependencies = [ [[package]] name = "sc-client-db" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "hash-db", "kvdb", @@ -9038,7 +9055,7 @@ dependencies = [ [[package]] name = "sc-consensus" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "async-trait", "futures", @@ -9063,7 +9080,7 @@ dependencies = [ [[package]] name = "sc-consensus-babe" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "async-trait", "fork-tree", @@ -9101,7 +9118,7 @@ dependencies = [ [[package]] name = "sc-consensus-babe-rpc" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "futures", "jsonrpsee", @@ -9123,7 +9140,7 @@ dependencies = [ [[package]] name = "sc-consensus-epochs" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "fork-tree", "parity-scale-codec", @@ -9136,7 +9153,7 @@ dependencies = [ [[package]] name = "sc-consensus-slots" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "async-trait", "futures", @@ -9159,7 +9176,7 @@ dependencies = [ [[package]] name = "sc-executor" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "lru", "parity-scale-codec", @@ -9183,7 +9200,7 @@ dependencies = [ [[package]] name = "sc-executor-common" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "sc-allocator", "sp-maybe-compressed-blob", @@ -9196,7 +9213,7 @@ dependencies = [ [[package]] name = "sc-executor-wasmi" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "log", "sc-allocator", @@ -9209,7 +9226,7 @@ dependencies = [ [[package]] name = "sc-executor-wasmtime" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "cfg-if", "libc", @@ -9226,7 +9243,7 @@ dependencies = [ [[package]] name = "sc-finality-grandpa" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "ahash", "array-bytes", @@ -9266,7 +9283,7 @@ dependencies = [ [[package]] name = "sc-finality-grandpa-rpc" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "finality-grandpa", "futures", @@ -9286,7 +9303,7 @@ dependencies = [ [[package]] name = "sc-informant" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "ansi_term", "futures", @@ -9301,7 +9318,7 @@ dependencies = [ [[package]] name = "sc-keystore" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "array-bytes", "async-trait", @@ -9316,7 +9333,7 @@ dependencies = [ [[package]] name = "sc-network" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "array-bytes", "async-trait", @@ -9358,7 +9375,7 @@ dependencies = [ [[package]] name = "sc-network-bitswap" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "cid", "futures", @@ -9377,7 +9394,7 @@ dependencies = [ [[package]] name = "sc-network-common" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "async-trait", "bitflags", @@ -9403,7 +9420,7 @@ dependencies = [ [[package]] name = "sc-network-gossip" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "ahash", "futures", @@ -9421,7 +9438,7 @@ dependencies = [ [[package]] name = "sc-network-light" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "array-bytes", "futures", @@ -9442,7 +9459,7 @@ dependencies = [ [[package]] name = "sc-network-sync" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "array-bytes", "async-trait", @@ -9474,7 +9491,7 @@ dependencies = [ [[package]] name = "sc-network-transactions" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "array-bytes", "futures", @@ -9493,7 +9510,7 @@ dependencies = [ [[package]] name = "sc-offchain" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "array-bytes", "bytes", @@ -9523,7 +9540,7 @@ dependencies = [ [[package]] name = "sc-peerset" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "futures", "libp2p", @@ -9536,7 +9553,7 @@ dependencies = [ [[package]] name = "sc-proposer-metrics" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "log", "substrate-prometheus-endpoint", @@ -9545,7 +9562,7 @@ dependencies = [ [[package]] name = "sc-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "futures", "jsonrpsee", @@ -9574,7 +9591,7 @@ dependencies = [ [[package]] name = "sc-rpc-api" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -9593,7 +9610,7 @@ dependencies = [ [[package]] name = "sc-rpc-server" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "http", "jsonrpsee", @@ -9608,7 +9625,7 @@ dependencies = [ [[package]] name = "sc-rpc-spec-v2" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "array-bytes", "futures", @@ -9634,7 +9651,7 @@ dependencies = [ [[package]] name = "sc-service" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "async-trait", "directories", @@ -9699,7 +9716,7 @@ dependencies = [ [[package]] name = "sc-state-db" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "log", "parity-scale-codec", @@ -9710,7 +9727,7 @@ dependencies = [ [[package]] name = "sc-sync-state-rpc" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -9729,7 +9746,7 @@ dependencies = [ [[package]] name = "sc-sysinfo" version = "6.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "futures", "libc", @@ -9748,7 +9765,7 @@ dependencies = [ [[package]] name = "sc-telemetry" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "chrono", "futures", @@ -9767,7 +9784,7 @@ dependencies = [ [[package]] name = "sc-tracing" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "ansi_term", "atty", @@ -9798,7 +9815,7 @@ dependencies = [ [[package]] name = "sc-tracing-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -9809,7 +9826,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "async-trait", "futures", @@ -9835,7 +9852,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "async-trait", "futures", @@ -9849,7 +9866,7 @@ dependencies = [ [[package]] name = "sc-utils" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "backtrace", "futures", @@ -10345,7 +10362,7 @@ dependencies = [ [[package]] name = "sp-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "hash-db", "log", @@ -10363,7 +10380,7 @@ dependencies = [ [[package]] name = "sp-api-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "blake2", "proc-macro-crate", @@ -10375,7 +10392,7 @@ dependencies = [ [[package]] name = "sp-application-crypto" version = "7.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "parity-scale-codec", "scale-info", @@ -10388,7 +10405,7 @@ dependencies = [ [[package]] name = "sp-arithmetic" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "integer-sqrt", "num-traits", @@ -10402,7 +10419,7 @@ dependencies = [ [[package]] name = "sp-authority-discovery" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "parity-scale-codec", "scale-info", @@ -10415,7 +10432,7 @@ dependencies = [ [[package]] name = "sp-authorship" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "async-trait", "parity-scale-codec", @@ -10427,7 +10444,7 @@ dependencies = [ [[package]] name = "sp-beefy" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "parity-scale-codec", "scale-info", @@ -10444,7 +10461,7 @@ dependencies = [ [[package]] name = "sp-block-builder" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "parity-scale-codec", "sp-api", @@ -10456,7 +10473,7 @@ dependencies = [ [[package]] name = "sp-blockchain" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "futures", "log", @@ -10474,7 +10491,7 @@ dependencies = [ [[package]] name = "sp-consensus" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "async-trait", "futures", @@ -10492,7 +10509,7 @@ dependencies = [ [[package]] name = "sp-consensus-babe" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "async-trait", "merlin", @@ -10515,7 +10532,7 @@ dependencies = [ [[package]] name = "sp-consensus-slots" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "parity-scale-codec", "scale-info", @@ -10527,7 +10544,7 @@ dependencies = [ [[package]] name = "sp-consensus-vrf" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "parity-scale-codec", "scale-info", @@ -10540,7 +10557,7 @@ dependencies = [ [[package]] name = "sp-core" version = "7.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "array-bytes", "base58", @@ -10582,7 +10599,7 @@ dependencies = [ [[package]] name = "sp-core-hashing" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "blake2", "byteorder", @@ -10596,7 +10613,7 @@ dependencies = [ [[package]] name = "sp-core-hashing-proc-macro" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "proc-macro2", "quote", @@ -10607,7 +10624,7 @@ dependencies = [ [[package]] name = "sp-database" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "kvdb", "parking_lot 0.12.1", @@ -10616,7 +10633,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "proc-macro2", "quote", @@ -10626,7 +10643,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.13.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "environmental", "parity-scale-codec", @@ -10637,7 +10654,7 @@ dependencies = [ [[package]] name = "sp-finality-grandpa" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "finality-grandpa", "log", @@ -10655,7 +10672,7 @@ dependencies = [ [[package]] name = "sp-inherents" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "async-trait", "impl-trait-for-tuples", @@ -10669,7 +10686,7 @@ dependencies = [ [[package]] name = "sp-io" version = "7.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "bytes", "ed25519", @@ -10694,7 +10711,7 @@ dependencies = [ [[package]] name = "sp-keyring" version = "7.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "lazy_static", "sp-core", @@ -10705,7 +10722,7 @@ dependencies = [ [[package]] name = "sp-keystore" version = "0.13.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "async-trait", "futures", @@ -10722,7 +10739,7 @@ dependencies = [ [[package]] name = "sp-maybe-compressed-blob" version = "4.1.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "thiserror", "zstd", @@ -10731,7 +10748,7 @@ dependencies = [ [[package]] name = "sp-mmr-primitives" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "ckb-merkle-mountain-range", "log", @@ -10749,7 +10766,7 @@ dependencies = [ [[package]] name = "sp-npos-elections" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "parity-scale-codec", "scale-info", @@ -10763,7 +10780,7 @@ dependencies = [ [[package]] name = "sp-offchain" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "sp-api", "sp-core", @@ -10773,7 +10790,7 @@ dependencies = [ [[package]] name = "sp-panic-handler" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "backtrace", "lazy_static", @@ -10783,7 +10800,7 @@ dependencies = [ [[package]] name = "sp-rpc" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "rustc-hash", "serde", @@ -10793,7 +10810,7 @@ dependencies = [ [[package]] name = "sp-runtime" version = "7.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "either", "hash256-std-hasher", @@ -10815,7 +10832,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "7.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -10833,7 +10850,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "Inflector", "proc-macro-crate", @@ -10845,7 +10862,7 @@ dependencies = [ [[package]] name = "sp-session" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "parity-scale-codec", "scale-info", @@ -10859,7 +10876,7 @@ dependencies = [ [[package]] name = "sp-staking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "parity-scale-codec", "scale-info", @@ -10871,7 +10888,7 @@ dependencies = [ [[package]] name = "sp-state-machine" version = "0.13.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "hash-db", "log", @@ -10891,12 +10908,12 @@ dependencies = [ [[package]] name = "sp-std" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" [[package]] name = "sp-storage" version = "7.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "impl-serde", "parity-scale-codec", @@ -10909,7 +10926,7 @@ dependencies = [ [[package]] name = "sp-timestamp" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "async-trait", "futures-timer", @@ -10924,7 +10941,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "parity-scale-codec", "sp-std", @@ -10936,7 +10953,7 @@ dependencies = [ [[package]] name = "sp-transaction-pool" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "sp-api", "sp-runtime", @@ -10945,7 +10962,7 @@ dependencies = [ [[package]] name = "sp-transaction-storage-proof" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "async-trait", "log", @@ -10961,7 +10978,7 @@ dependencies = [ [[package]] name = "sp-trie" version = "7.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "ahash", "hash-db", @@ -10984,7 +11001,7 @@ dependencies = [ [[package]] name = "sp-version" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "impl-serde", "parity-scale-codec", @@ -11001,7 +11018,7 @@ dependencies = [ [[package]] name = "sp-version-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "parity-scale-codec", "proc-macro2", @@ -11012,7 +11029,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "7.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "impl-trait-for-tuples", "log", @@ -11025,7 +11042,7 @@ dependencies = [ [[package]] name = "sp-weights" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "parity-scale-codec", "scale-info", @@ -11258,7 +11275,7 @@ dependencies = [ [[package]] name = "substrate-build-script-utils" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "platforms", ] @@ -11266,7 +11283,7 @@ dependencies = [ [[package]] name = "substrate-frame-rpc-system" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "frame-system-rpc-runtime-api", "futures", @@ -11285,7 +11302,7 @@ dependencies = [ [[package]] name = "substrate-prometheus-endpoint" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "hyper", "log", @@ -11297,7 +11314,7 @@ dependencies = [ [[package]] name = "substrate-rpc-client" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "async-trait", "jsonrpsee", @@ -11310,7 +11327,7 @@ dependencies = [ [[package]] name = "substrate-state-trie-migration-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "jsonrpsee", "log", @@ -11329,7 +11346,7 @@ dependencies = [ [[package]] name = "substrate-test-client" version = "2.0.1" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "array-bytes", "async-trait", @@ -11355,7 +11372,7 @@ dependencies = [ [[package]] name = "substrate-test-utils" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "futures", "substrate-test-utils-derive", @@ -11365,7 +11382,7 @@ dependencies = [ [[package]] name = "substrate-test-utils-derive" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -11376,7 +11393,7 @@ dependencies = [ [[package]] name = "substrate-wasm-builder" version = "5.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "ansi_term", "build-helper", @@ -12177,7 +12194,7 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "try-runtime-cli" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#934d42aefb51b797ee9ef41270bc041b1c1c6025" +source = "git+https://github.com/paritytech/substrate?branch=master#70f2e364ab1ad57ce8d3abe228222b299393c5c0" dependencies = [ "clap 4.0.15", "frame-remote-externalities", @@ -12207,9 +12224,9 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.61" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc92f558afb6d1d7c6f175eb8d615b8ef49c227543e68e19c123d4ee43d8a7d" +checksum = "f1212c215a87a183687a7cc7065901b1a98da6b37277d51a1b5faedbb4efd4f3" dependencies = [ "dissimilar", "glob", @@ -13540,7 +13557,10 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-runtime", + "serde", + "sp-core", + "sp-io", + "sp-weights", "xcm-procedural", ] @@ -13548,8 +13568,10 @@ dependencies = [ name = "xcm-builder" version = "0.9.33" dependencies = [ + "assert_matches", "frame-support", "frame-system", + "impl-trait-for-tuples", "log", "pallet-balances", "pallet-transaction-payment", @@ -13571,6 +13593,7 @@ dependencies = [ name = "xcm-executor" version = "0.9.33" dependencies = [ + "environmental", "frame-benchmarking", "frame-support", "impl-trait-for-tuples", @@ -13581,6 +13604,7 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", + "sp-weights", "xcm", ] @@ -13636,7 +13660,9 @@ version = "0.9.33" dependencies = [ "frame-support", "frame-system", + "log", "pallet-balances", + "pallet-uniques", "pallet-xcm", "parity-scale-codec", "polkadot-core-primitives", @@ -13647,6 +13673,7 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", + "sp-tracing", "xcm", "xcm-builder", "xcm-executor", diff --git a/node/core/pvf/src/host.rs b/node/core/pvf/src/host.rs index 3514aff1d896..98a945be26f6 100644 --- a/node/core/pvf/src/host.rs +++ b/node/core/pvf/src/host.rs @@ -354,7 +354,7 @@ async fn run( futures::select_biased! { () = cleanup_pulse.select_next_some() => { // `select_next_some` because we don't expect this to fail, but if it does, we - // still don't fail. The tradeoff is that the compiled cache will start growing + // still don't fail. The trade-off is that the compiled cache will start growing // in size. That is, however, rather a slow process and hopefully the operator // will notice it. diff --git a/node/network/protocol/src/request_response/mod.rs b/node/network/protocol/src/request_response/mod.rs index 555c50fd5913..a12905f94ff0 100644 --- a/node/network/protocol/src/request_response/mod.rs +++ b/node/network/protocol/src/request_response/mod.rs @@ -104,7 +104,7 @@ const STATEMENTS_TIMEOUT: Duration = Duration::from_secs(1); /// We don't want a slow peer to slow down all the others, at the same time we want to get out the /// data quickly in full to at least some peers (as this will reduce load on us as they then can -/// start serving the data). So this value is a tradeoff. 3 seems to be sensible. So we would need +/// start serving the data). So this value is a trade-off. 3 seems to be sensible. So we would need /// to have 3 slow nodes connected, to delay transfer for others by `STATEMENTS_TIMEOUT`. pub const MAX_PARALLEL_STATEMENT_REQUESTS: u32 = 3; diff --git a/node/service/src/chain_spec.rs b/node/service/src/chain_spec.rs index 1f59c5ec4b3d..12b65c7ba853 100644 --- a/node/service/src/chain_spec.rs +++ b/node/service/src/chain_spec.rs @@ -189,8 +189,7 @@ fn default_parachains_host_configuration( max_upward_queue_count: 8, max_upward_queue_size: 1024 * 1024, max_downward_message_size: 1024 * 1024, - ump_service_total_weight: Weight::from_ref_time(100_000_000_000) - .set_proof_size(MAX_POV_SIZE as u64), + ump_service_total_weight: Weight::from_parts(100_000_000_000, MAX_POV_SIZE as u64), max_upward_message_size: 50 * 1024, max_upward_message_num_per_candidate: 5, hrmp_sender_deposit: 0, diff --git a/runtime/common/src/impls.rs b/runtime/common/src/impls.rs index 77b168fb7a4f..942fc3373c9c 100644 --- a/runtime/common/src/impls.rs +++ b/runtime/common/src/impls.rs @@ -138,7 +138,7 @@ mod tests { weight.base_extrinsic = Weight::from_ref_time(100); }) .for_class(DispatchClass::non_mandatory(), |weight| { - weight.max_total = Some(Weight::from_ref_time(1024).set_proof_size(u64::MAX)); + weight.max_total = Some(Weight::from_parts(1024, u64::MAX)); }) .build_or_panic(); pub BlockLength: limits::BlockLength = limits::BlockLength::max(2 * 1024); diff --git a/runtime/common/src/integration_tests.rs b/runtime/common/src/integration_tests.rs index 4a4ebe7dc462..be506a26561e 100644 --- a/runtime/common/src/integration_tests.rs +++ b/runtime/common/src/integration_tests.rs @@ -103,7 +103,7 @@ parameter_types! { pub const BlockHashCount: u32 = 250; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max( - Weight::from_ref_time(4 * 1024 * 1024).set_proof_size(u64::MAX), + Weight::from_parts(4 * 1024 * 1024, u64::MAX), ); } diff --git a/runtime/common/src/paras_registrar.rs b/runtime/common/src/paras_registrar.rs index 9588d2ce833c..ec65cac94414 100644 --- a/runtime/common/src/paras_registrar.rs +++ b/runtime/common/src/paras_registrar.rs @@ -705,9 +705,7 @@ mod tests { parameter_types! { pub const BlockHashCount: u32 = 250; pub BlockWeights: limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max( - Weight::from_ref_time(1024).set_proof_size(u64::MAX), - ); + frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, u64::MAX)); pub BlockLength: limits::BlockLength = limits::BlockLength::max_with_normal_ratio(4 * 1024 * 1024, NORMAL_RATIO); } diff --git a/runtime/common/src/xcm_sender.rs b/runtime/common/src/xcm_sender.rs index 2d75edfd4571..392f0a40a087 100644 --- a/runtime/common/src/xcm_sender.rs +++ b/runtime/common/src/xcm_sender.rs @@ -16,34 +16,75 @@ //! XCM sender for relay chain. +use frame_support::traits::Get; use parity_scale_codec::Encode; -use runtime_parachains::{configuration, dmp}; -use sp_std::marker::PhantomData; -use xcm::latest::prelude::*; +use primitives::v2::Id as ParaId; +use runtime_parachains::{ + configuration::{self, HostConfiguration}, + dmp, +}; +use sp_std::{marker::PhantomData, prelude::*}; +use xcm::prelude::*; +use SendError::*; + +/// Simple value-bearing trait for determining/expressing the assets required to be paid for a +/// messages to be delivered to a parachain. +pub trait PriceForParachainDelivery { + /// Return the assets required to deliver `message` to the given `para` destination. + fn price_for_parachain_delivery(para: ParaId, message: &Xcm<()>) -> MultiAssets; +} +impl PriceForParachainDelivery for () { + fn price_for_parachain_delivery(_: ParaId, _: &Xcm<()>) -> MultiAssets { + MultiAssets::new() + } +} + +/// Implementation of `PriceForParachainDelivery` which returns a fixed price. +pub struct ConstantPrice(sp_std::marker::PhantomData); +impl> PriceForParachainDelivery for ConstantPrice { + fn price_for_parachain_delivery(_: ParaId, _: &Xcm<()>) -> MultiAssets { + T::get() + } +} /// XCM sender for relay chain. It only sends downward message. -pub struct ChildParachainRouter(PhantomData<(T, W)>); +pub struct ChildParachainRouter(PhantomData<(T, W, P)>); -impl SendXcm - for ChildParachainRouter +impl + SendXcm for ChildParachainRouter { - fn send_xcm(dest: impl Into, msg: Xcm<()>) -> SendResult { - let dest = dest.into(); - match dest { - MultiLocation { parents: 0, interior: X1(Parachain(id)) } => { - // Downward message passing. - let versioned_xcm = - W::wrap_version(&dest, msg).map_err(|()| SendError::DestinationUnsupported)?; - let config = >::config(); - >::queue_downward_message( - &config, - id.into(), - versioned_xcm.encode(), - ) - .map_err(Into::::into)?; - Ok(()) - }, - dest => Err(SendError::CannotReachDestination(dest, msg)), - } + type Ticket = (HostConfiguration, ParaId, Vec); + + fn validate( + dest: &mut Option, + msg: &mut Option>, + ) -> SendResult<(HostConfiguration, ParaId, Vec)> { + let d = dest.take().ok_or(MissingArgument)?; + let id = if let MultiLocation { parents: 0, interior: X1(Parachain(id)) } = &d { + *id + } else { + *dest = Some(d); + return Err(NotApplicable) + }; + + // Downward message passing. + let xcm = msg.take().ok_or(MissingArgument)?; + let config = >::config(); + let para = id.into(); + let price = P::price_for_parachain_delivery(para, &xcm); + let blob = W::wrap_version(&d, xcm).map_err(|()| DestinationUnsupported)?.encode(); + >::can_queue_downward_message(&config, ¶, &blob) + .map_err(Into::::into)?; + + Ok(((config, para, blob), price)) + } + + fn deliver( + (config, para, blob): (HostConfiguration, ParaId, Vec), + ) -> Result { + let hash = sp_io::hashing::blake2_256(&blob[..]); + >::queue_downward_message(&config, para, blob) + .map(|()| hash) + .map_err(|_| SendError::Transport(&"Error placing into DMP queue")) } } diff --git a/runtime/kusama/src/lib.rs b/runtime/kusama/src/lib.rs index dcb9a452a715..f6dfdbf1ef59 100644 --- a/runtime/kusama/src/lib.rs +++ b/runtime/kusama/src/lib.rs @@ -1497,6 +1497,9 @@ pub type Migrations = ( pallet_staking::migrations::v13::MigrateToV13, parachains_disputes::migration::v1::MigrateToV1, parachains_configuration::migration::v4::MigrateToV4, + // "Use 2D weights in XCM v3" + pallet_xcm::migration::v1::MigrateToV1, + parachains_ump::migration::v1::MigrateToV1, ); /// Unchecked extrinsic type as expected by this runtime. @@ -1576,6 +1579,7 @@ mod benches { [pallet_vesting, Vesting] [pallet_whitelist, Whitelist] // XCM + [pallet_xcm, XcmPallet] [pallet_xcm_benchmarks::fungible, pallet_xcm_benchmarks::fungible::Pallet::] [pallet_xcm_benchmarks::generic, pallet_xcm_benchmarks::generic::Pallet::] ); @@ -1978,7 +1982,9 @@ sp_api::impl_runtime_apis! { use pallet_nomination_pools_benchmarking::Pallet as NominationPoolsBench; use frame_benchmarking::baseline::Pallet as Baseline; use xcm::latest::prelude::*; - use xcm_config::{CheckAccount, KsmLocation, SovereignAccountOf, Statemine, XcmConfig}; + use xcm_config::{ + LocalCheckAccount, SovereignAccountOf, Statemine, TokenLocation, XcmConfig, + }; impl pallet_session_benchmarking::Config for Runtime {} impl pallet_offences_benchmarking::Config for Runtime {} @@ -1993,10 +1999,10 @@ sp_api::impl_runtime_apis! { fn valid_destination() -> Result { Ok(Statemine::get()) } - fn worst_case_holding() -> MultiAssets { + fn worst_case_holding(_depositable_count: u32) -> MultiAssets { // Kusama only knows about KSM. vec![MultiAsset{ - id: Concrete(KsmLocation::get()), + id: Concrete(TokenLocation::get()), fun: Fungible(1_000_000 * UNITS), }].into() } @@ -2005,24 +2011,19 @@ sp_api::impl_runtime_apis! { parameter_types! { pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( Statemine::get(), - MultiAsset { fun: Fungible(1 * UNITS), id: Concrete(KsmLocation::get()) }, - )); - pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = Some(( - Statemine::get(), - MultiAsset { fun: Fungible(1 * UNITS), id: Concrete(KsmLocation::get()) }, + MultiAsset { fun: Fungible(1 * UNITS), id: Concrete(TokenLocation::get()) }, )); } impl pallet_xcm_benchmarks::fungible::Config for Runtime { type TransactAsset = Balances; - type CheckedAccount = CheckAccount; + type CheckedAccount = LocalCheckAccount; type TrustedTeleporter = TrustedTeleporter; - type TrustedReserve = TrustedReserve; fn get_multi_asset() -> MultiAsset { MultiAsset { - id: Concrete(KsmLocation::get()), + id: Concrete(TokenLocation::get()), fun: Fungible(1 * UNITS), } } @@ -2035,8 +2036,18 @@ sp_api::impl_runtime_apis! { (0u64, Response::Version(Default::default())) } - fn transact_origin() -> Result { - Ok(Statemine::get()) + fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + // Kusama doesn't support asset exchanges + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result { + // The XCM executor of Kusama doesn't have a configured `UniversalAliases` + Err(BenchmarkError::Skip) + } + + fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { + Ok((Statemine::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) } fn subscribe_origin() -> Result { @@ -2045,10 +2056,15 @@ sp_api::impl_runtime_apis! { fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { let origin = Statemine::get(); - let assets: MultiAssets = (Concrete(KsmLocation::get()), 1_000 * UNITS).into(); + let assets: MultiAssets = (Concrete(TokenLocation::get()), 1_000 * UNITS).into(); let ticket = MultiLocation { parents: 0, interior: Here }; Ok((origin, ticket, assets)) } + + fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + // Kusama doesn't support asset locking + Err(BenchmarkError::Skip) + } } let whitelist: Vec = vec![ diff --git a/runtime/kusama/src/weights/mod.rs b/runtime/kusama/src/weights/mod.rs index c277b2beb819..014204074484 100644 --- a/runtime/kusama/src/weights/mod.rs +++ b/runtime/kusama/src/weights/mod.rs @@ -50,6 +50,7 @@ pub mod pallet_treasury; pub mod pallet_utility; pub mod pallet_vesting; pub mod pallet_whitelist; +pub mod pallet_xcm; pub mod runtime_common_auctions; pub mod runtime_common_claims; pub mod runtime_common_crowdloan; diff --git a/runtime/kusama/src/weights/pallet_xcm.rs b/runtime/kusama/src/weights/pallet_xcm.rs new file mode 100644 index 000000000000..fc7d75768aff --- /dev/null +++ b/runtime/kusama/src/weights/pallet_xcm.rs @@ -0,0 +1,168 @@ +// Copyright 2017-2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . +//! Autogenerated weights for `pallet_xcm` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-12-16, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("kusama-dev"), DB CACHE: 1024 + +// Executed Command: +// /home/benchbot/cargo_target_dir/production/polkadot +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/polkadot/.git/.artifacts/bench.json +// --pallet=pallet_xcm +// --chain=kusama-dev +// --header=./file_header.txt +// --output=./runtime/kusama/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weight functions for `pallet_xcm`. +pub struct WeightInfo(PhantomData); +impl pallet_xcm::WeightInfo for WeightInfo { + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + fn send() -> Weight { + // Minimum execution time: 36_474 nanoseconds. + Weight::from_ref_time(37_030_000) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(3)) + } + fn teleport_assets() -> Weight { + // Minimum execution time: 28_147 nanoseconds. + Weight::from_ref_time(28_836_000) + } + fn reserve_transfer_assets() -> Weight { + // Minimum execution time: 28_469 nanoseconds. + Weight::from_ref_time(29_002_000) + } + fn execute() -> Weight { + // Minimum execution time: 15_637 nanoseconds. + Weight::from_ref_time(15_880_000) + } + // Storage: XcmPallet SupportedVersion (r:0 w:1) + fn force_xcm_version() -> Weight { + // Minimum execution time: 15_330 nanoseconds. + Weight::from_ref_time(15_817_000) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: XcmPallet SafeXcmVersion (r:0 w:1) + fn force_default_xcm_version() -> Weight { + // Minimum execution time: 4_104 nanoseconds. + Weight::from_ref_time(4_365_000) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: XcmPallet VersionNotifiers (r:1 w:1) + // Storage: XcmPallet QueryCounter (r:1 w:1) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + // Storage: XcmPallet Queries (r:0 w:1) + fn force_subscribe_version_notify() -> Weight { + // Minimum execution time: 42_177 nanoseconds. + Weight::from_ref_time(42_657_000) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(6)) + } + // Storage: XcmPallet VersionNotifiers (r:1 w:1) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + // Storage: XcmPallet Queries (r:0 w:1) + fn force_unsubscribe_version_notify() -> Weight { + // Minimum execution time: 45_481 nanoseconds. + Weight::from_ref_time(45_960_000) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(5)) + } + // Storage: XcmPallet SupportedVersion (r:4 w:2) + fn migrate_supported_version() -> Weight { + // Minimum execution time: 14_899 nanoseconds. + Weight::from_ref_time(15_452_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: XcmPallet VersionNotifiers (r:4 w:2) + fn migrate_version_notifiers() -> Weight { + // Minimum execution time: 14_759 nanoseconds. + Weight::from_ref_time(15_176_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: XcmPallet VersionNotifyTargets (r:5 w:0) + fn already_notified_target() -> Weight { + // Minimum execution time: 17_022 nanoseconds. + Weight::from_ref_time(17_468_000) + .saturating_add(T::DbWeight::get().reads(5)) + } + // Storage: XcmPallet VersionNotifyTargets (r:2 w:1) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + fn notify_current_targets() -> Weight { + // Minimum execution time: 37_810 nanoseconds. + Weight::from_ref_time(38_198_000) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: XcmPallet VersionNotifyTargets (r:3 w:0) + fn notify_target_migration_fail() -> Weight { + // Minimum execution time: 7_440 nanoseconds. + Weight::from_ref_time(7_659_000) + .saturating_add(T::DbWeight::get().reads(3)) + } + // Storage: XcmPallet VersionNotifyTargets (r:4 w:2) + fn migrate_version_notify_targets() -> Weight { + // Minimum execution time: 14_975 nanoseconds. + Weight::from_ref_time(15_479_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: XcmPallet VersionNotifyTargets (r:4 w:2) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + fn migrate_and_notify_old_targets() -> Weight { + // Minimum execution time: 43_328 nanoseconds. + Weight::from_ref_time(44_054_000) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(5)) + } +} diff --git a/runtime/kusama/src/weights/runtime_parachains_ump.rs b/runtime/kusama/src/weights/runtime_parachains_ump.rs index 1473eeac063b..bbdc0a9b5d8f 100644 --- a/runtime/kusama/src/weights/runtime_parachains_ump.rs +++ b/runtime/kusama/src/weights/runtime_parachains_ump.rs @@ -16,23 +16,25 @@ //! Autogenerated weights for `runtime_parachains::ump` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-16, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm5`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-01-16, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("kusama-dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// /home/benchbot/cargo_target_dir/production/polkadot // benchmark // pallet -// --chain=kusama-dev // --steps=50 // --repeat=20 -// --pallet=runtime_parachains::ump // --extrinsic=* // --execution=wasm // --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/polkadot/.git/.artifacts/bench.json +// --pallet=runtime_parachains::ump +// --chain=kusama-dev // --header=./file_header.txt -// --output=./runtime/kusama/src/weights/runtime_parachains_ump.rs +// --output=./runtime/kusama/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -46,26 +48,27 @@ pub struct WeightInfo(PhantomData); impl runtime_parachains::ump::WeightInfo for WeightInfo { /// The range of component `s` is `[0, 51200]`. fn process_upward_message(s: u32, ) -> Weight { - // Minimum execution time: 10_348 nanoseconds. - Weight::from_ref_time(5_121_205 as u64) - // Standard Error: 12 - .saturating_add(Weight::from_ref_time(1_934 as u64).saturating_mul(s as u64)) + // Minimum execution time: 10_393 nanoseconds. + Weight::from_ref_time(2_845_995) + // Standard Error: 21 + .saturating_add(Weight::from_ref_time(2_016).saturating_mul(s.into())) } // Storage: Ump NeedsDispatch (r:1 w:1) // Storage: Ump NextDispatchRoundStartWith (r:1 w:1) // Storage: Ump RelayDispatchQueues (r:0 w:1) // Storage: Ump RelayDispatchQueueSize (r:0 w:1) fn clean_ump_after_outgoing() -> Weight { - // Minimum execution time: 9_800 nanoseconds. - Weight::from_ref_time(10_025_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 9_686 nanoseconds. + Weight::from_ref_time(9_920_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Ump Overweight (r:1 w:1) + // Storage: Ump CounterForOverweight (r:1 w:1) fn service_overweight() -> Weight { - // Minimum execution time: 26_272 nanoseconds. - Weight::from_ref_time(26_790_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 28_502 nanoseconds. + Weight::from_ref_time(28_900_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } } diff --git a/runtime/kusama/src/weights/xcm/mod.rs b/runtime/kusama/src/weights/xcm/mod.rs index f03904b799a4..74c67c175785 100644 --- a/runtime/kusama/src/weights/xcm/mod.rs +++ b/runtime/kusama/src/weights/xcm/mod.rs @@ -4,10 +4,7 @@ mod pallet_xcm_benchmarks_generic; use crate::Runtime; use frame_support::weights::Weight; use sp_std::prelude::*; -use xcm::{ - latest::{prelude::*, Weight as XCMWeight}, - DoubleEncoded, -}; +use xcm::{latest::prelude::*, DoubleEncoded}; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmBalancesWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; @@ -31,15 +28,15 @@ impl From<&MultiAsset> for AssetTypes { } trait WeighMultiAssets { - fn weigh_multi_assets(&self, balances_weight: Weight) -> XCMWeight; + fn weigh_multi_assets(&self, balances_weight: Weight) -> Weight; } // Kusama only knows about one asset, the balances pallet. const MAX_ASSETS: u32 = 1; impl WeighMultiAssets for MultiAssetFilter { - fn weigh_multi_assets(&self, balances_weight: Weight) -> XCMWeight { - let weight = match self { + fn weigh_multi_assets(&self, balances_weight: Weight) -> Weight { + match self { Self::Definite(assets) => assets .inner() .into_iter() @@ -49,155 +46,213 @@ impl WeighMultiAssets for MultiAssetFilter { AssetTypes::Unknown => Weight::MAX, }) .fold(Weight::zero(), |acc, x| acc.saturating_add(x)), - Self::Wild(_) => balances_weight.saturating_mul(MAX_ASSETS as u64), - }; - - weight.ref_time() + Self::Wild(AllOf { .. } | AllOfCounted { .. }) => balances_weight, + Self::Wild(AllCounted(count)) => balances_weight.saturating_mul(*count as u64), + Self::Wild(All) => balances_weight.saturating_mul(MAX_ASSETS as u64), + } } } impl WeighMultiAssets for MultiAssets { - fn weigh_multi_assets(&self, balances_weight: Weight) -> XCMWeight { - let weight = self - .inner() + fn weigh_multi_assets(&self, balances_weight: Weight) -> Weight { + self.inner() .into_iter() .map(|m| >::from(m)) .map(|t| match t { AssetTypes::Balances => balances_weight, AssetTypes::Unknown => Weight::MAX, }) - .fold(Weight::zero(), |acc, x| acc.saturating_add(x)); - - weight.ref_time() + .fold(Weight::zero(), |acc, x| acc.saturating_add(x)) } } pub struct KusamaXcmWeight(core::marker::PhantomData); impl XcmWeightInfo for KusamaXcmWeight { - fn withdraw_asset(assets: &MultiAssets) -> XCMWeight { + fn withdraw_asset(assets: &MultiAssets) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::withdraw_asset()) } - fn reserve_asset_deposited(assets: &MultiAssets) -> XCMWeight { + fn reserve_asset_deposited(assets: &MultiAssets) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::reserve_asset_deposited()) } - fn receive_teleported_asset(assets: &MultiAssets) -> XCMWeight { + fn receive_teleported_asset(assets: &MultiAssets) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::receive_teleported_asset()) } - fn query_response(_query_id: &u64, _response: &Response, _max_weight: &u64) -> XCMWeight { - XcmGeneric::::query_response().ref_time() + fn query_response( + _query_id: &u64, + _response: &Response, + _max_weight: &Weight, + _querier: &Option, + ) -> Weight { + XcmGeneric::::query_response() } - fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> XCMWeight { + fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::transfer_asset()) } fn transfer_reserve_asset( assets: &MultiAssets, _dest: &MultiLocation, _xcm: &Xcm<()>, - ) -> XCMWeight { + ) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::transfer_reserve_asset()) } fn transact( - _origin_type: &OriginKind, - _require_weight_at_most: &u64, + _origin_kind: &OriginKind, + _require_weight_at_most: &Weight, _call: &DoubleEncoded, - ) -> XCMWeight { - XcmGeneric::::transact().ref_time() + ) -> Weight { + XcmGeneric::::transact() } fn hrmp_new_channel_open_request( _sender: &u32, _max_message_size: &u32, _max_capacity: &u32, - ) -> XCMWeight { + ) -> Weight { // XCM Executor does not currently support HRMP channel operations - Weight::MAX.ref_time() + Weight::MAX } - fn hrmp_channel_accepted(_recipient: &u32) -> XCMWeight { + fn hrmp_channel_accepted(_recipient: &u32) -> Weight { // XCM Executor does not currently support HRMP channel operations - Weight::MAX.ref_time() + Weight::MAX } - fn hrmp_channel_closing(_initiator: &u32, _sender: &u32, _recipient: &u32) -> XCMWeight { + fn hrmp_channel_closing(_initiator: &u32, _sender: &u32, _recipient: &u32) -> Weight { // XCM Executor does not currently support HRMP channel operations - Weight::MAX.ref_time() + Weight::MAX } - fn clear_origin() -> XCMWeight { - XcmGeneric::::clear_origin().ref_time() + fn clear_origin() -> Weight { + XcmGeneric::::clear_origin() } - fn descend_origin(_who: &InteriorMultiLocation) -> XCMWeight { - XcmGeneric::::descend_origin().ref_time() + fn descend_origin(_who: &InteriorMultiLocation) -> Weight { + XcmGeneric::::descend_origin() } - fn report_error( - _query_id: &QueryId, - _dest: &MultiLocation, - _max_response_weight: &u64, - ) -> XCMWeight { - XcmGeneric::::report_error().ref_time() + fn report_error(_query_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_error() } - fn deposit_asset( - assets: &MultiAssetFilter, - _max_assets: &u32, // TODO use max assets? - _dest: &MultiLocation, - ) -> XCMWeight { + fn deposit_asset(assets: &MultiAssetFilter, _dest: &MultiLocation) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::deposit_asset()) } fn deposit_reserve_asset( assets: &MultiAssetFilter, - _max_assets: &u32, // TODO use max assets? _dest: &MultiLocation, _xcm: &Xcm<()>, - ) -> XCMWeight { + ) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets) -> XCMWeight { - Weight::MAX.ref_time() // todo fix + fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets, _maximal: &bool) -> Weight { + // Kusama does not currently support exchange asset operations + Weight::MAX } fn initiate_reserve_withdraw( assets: &MultiAssetFilter, _reserve: &MultiLocation, _xcm: &Xcm<()>, - ) -> XCMWeight { + ) -> Weight { assets.weigh_multi_assets(XcmGeneric::::initiate_reserve_withdraw()) } fn initiate_teleport( assets: &MultiAssetFilter, _dest: &MultiLocation, _xcm: &Xcm<()>, - ) -> XCMWeight { + ) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::initiate_teleport()) } - fn query_holding( - _query_id: &u64, - _dest: &MultiLocation, - _assets: &MultiAssetFilter, - _max_response_weight: &u64, - ) -> XCMWeight { - XcmGeneric::::query_holding().ref_time() + fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + XcmGeneric::::report_holding() + } + fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { + XcmGeneric::::buy_execution() + } + fn refund_surplus() -> Weight { + XcmGeneric::::refund_surplus() + } + fn set_error_handler(_xcm: &Xcm) -> Weight { + XcmGeneric::::set_error_handler() + } + fn set_appendix(_xcm: &Xcm) -> Weight { + XcmGeneric::::set_appendix() + } + fn clear_error() -> Weight { + XcmGeneric::::clear_error() + } + fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> Weight { + XcmGeneric::::claim_asset() + } + fn trap(_code: &u64) -> Weight { + XcmGeneric::::trap() + } + fn subscribe_version(_query_id: &QueryId, _max_response_weight: &Weight) -> Weight { + XcmGeneric::::subscribe_version() + } + fn unsubscribe_version() -> Weight { + XcmGeneric::::unsubscribe_version() + } + fn burn_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + } + fn expect_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + } + fn expect_origin(_origin: &Option) -> Weight { + XcmGeneric::::expect_origin() + } + fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { + XcmGeneric::::expect_error() + } + fn query_pallet(_module_name: &Vec, _response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::query_pallet() + } + fn expect_pallet( + _index: &u32, + _name: &Vec, + _module_name: &Vec, + _crate_major: &u32, + _min_crate_minor: &u32, + ) -> Weight { + XcmGeneric::::expect_pallet() + } + fn report_transact_status(_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_transact_status() + } + fn clear_transact_status() -> Weight { + XcmGeneric::::clear_transact_status() + } + fn universal_origin(_: &Junction) -> Weight { + // Kusama does not currently support universal origin operations + Weight::MAX + } + fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { + Weight::MAX // todo fix } - fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> XCMWeight { - XcmGeneric::::buy_execution().ref_time() + fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Kusama does not currently support asset locking operations + Weight::MAX } - fn refund_surplus() -> XCMWeight { - XcmGeneric::::refund_surplus().ref_time() + fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Kusama does not currently support asset locking operations + Weight::MAX } - fn set_error_handler(_xcm: &Xcm) -> XCMWeight { - XcmGeneric::::set_error_handler().ref_time() + fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Kusama does not currently support asset locking operations + Weight::MAX } - fn set_appendix(_xcm: &Xcm) -> XCMWeight { - XcmGeneric::::set_appendix().ref_time() + fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Kusama does not currently support asset locking operations + Weight::MAX } - fn clear_error() -> XCMWeight { - XcmGeneric::::clear_error().ref_time() + fn set_fees_mode(_: &bool) -> Weight { + XcmGeneric::::set_fees_mode() } - fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> XCMWeight { - XcmGeneric::::claim_asset().ref_time() + fn set_topic(_topic: &[u8; 32]) -> Weight { + XcmGeneric::::set_topic() } - fn trap(_code: &u64) -> XCMWeight { - XcmGeneric::::trap().ref_time() + fn clear_topic() -> Weight { + XcmGeneric::::clear_topic() } - fn subscribe_version(_query_id: &QueryId, _max_response_weight: &u64) -> XCMWeight { - XcmGeneric::::subscribe_version().ref_time() + fn alias_origin(_: &MultiLocation) -> Weight { + // XCM Executor does not currently support alias origin operations + Weight::MAX } - fn unsubscribe_version() -> XCMWeight { - XcmGeneric::::unsubscribe_version().ref_time() + fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + XcmGeneric::::unpaid_execution() } } diff --git a/runtime/kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/runtime/kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 36f9c602f566..69421bdb414b 100644 --- a/runtime/kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/runtime/kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -17,12 +17,13 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-03-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-04-18, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("kusama-dev"), DB CACHE: 1024 // Executed Command: // target/production/polkadot // benchmark +// pallet // --chain=kusama-dev // --steps=50 // --repeat=20 @@ -48,89 +49,135 @@ impl WeightInfo { // Storage: XcmPallet SupportedVersion (r:1 w:0) // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) // Storage: XcmPallet SafeXcmVersion (r:1 w:0) - // Storage: Configuration ActiveConfig (r:1 w:0) // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) // Storage: Dmp DownwardMessageQueues (r:1 w:1) - pub(crate) fn query_holding() -> Weight { - Weight::from_ref_time(21_822_000 as u64) - .saturating_add(T::DbWeight::get().reads(6 as u64)) + pub(crate) fn report_holding() -> Weight { + Weight::from_ref_time(25_878_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } pub(crate) fn buy_execution() -> Weight { - Weight::from_ref_time(3_109_000 as u64) + Weight::from_ref_time(3_697_000 as u64) } // Storage: XcmPallet Queries (r:1 w:0) pub(crate) fn query_response() -> Weight { - Weight::from_ref_time(12_087_000 as u64) + Weight::from_ref_time(13_458_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) } pub(crate) fn transact() -> Weight { - Weight::from_ref_time(12_398_000 as u64) + Weight::from_ref_time(13_597_000 as u64) } pub(crate) fn refund_surplus() -> Weight { - Weight::from_ref_time(3_247_000 as u64) + Weight::from_ref_time(3_839_000 as u64) } pub(crate) fn set_error_handler() -> Weight { - Weight::from_ref_time(3_086_000 as u64) + Weight::from_ref_time(3_657_000 as u64) } pub(crate) fn set_appendix() -> Weight { - Weight::from_ref_time(3_112_000 as u64) + Weight::from_ref_time(3_757_000 as u64) } pub(crate) fn clear_error() -> Weight { - Weight::from_ref_time(3_118_000 as u64) + Weight::from_ref_time(3_651_000 as u64) } pub(crate) fn descend_origin() -> Weight { - Weight::from_ref_time(4_054_000 as u64) + Weight::from_ref_time(4_589_000 as u64) } pub(crate) fn clear_origin() -> Weight { - Weight::from_ref_time(3_111_000 as u64) + Weight::from_ref_time(3_661_000 as u64) } // Storage: XcmPallet SupportedVersion (r:1 w:0) // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) // Storage: XcmPallet SafeXcmVersion (r:1 w:0) - // Storage: Configuration ActiveConfig (r:1 w:0) // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) // Storage: Dmp DownwardMessageQueues (r:1 w:1) pub(crate) fn report_error() -> Weight { - Weight::from_ref_time(18_425_000 as u64) - .saturating_add(T::DbWeight::get().reads(6 as u64)) + Weight::from_ref_time(21_351_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: XcmPallet AssetTraps (r:1 w:1) pub(crate) fn claim_asset() -> Weight { - Weight::from_ref_time(7_144_000 as u64) + Weight::from_ref_time(7_674_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } pub(crate) fn trap() -> Weight { - Weight::from_ref_time(3_060_000 as u64) + Weight::from_ref_time(3_606_000 as u64) } // Storage: XcmPallet VersionNotifyTargets (r:1 w:1) // Storage: XcmPallet SupportedVersion (r:1 w:0) // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) // Storage: XcmPallet SafeXcmVersion (r:1 w:0) - // Storage: Configuration ActiveConfig (r:1 w:0) // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) // Storage: Dmp DownwardMessageQueues (r:1 w:1) pub(crate) fn subscribe_version() -> Weight { - Weight::from_ref_time(21_642_000 as u64) - .saturating_add(T::DbWeight::get().reads(7 as u64)) + Weight::from_ref_time(30_453_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: XcmPallet VersionNotifyTargets (r:0 w:1) pub(crate) fn unsubscribe_version() -> Weight { - Weight::from_ref_time(4_873_000 as u64) + Weight::from_ref_time(5_543_000 as u64) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: XcmPallet SupportedVersion (r:1 w:0) // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) // Storage: XcmPallet SafeXcmVersion (r:1 w:0) - // Storage: Configuration ActiveConfig (r:1 w:0) // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) // Storage: Dmp DownwardMessageQueues (r:1 w:1) pub(crate) fn initiate_reserve_withdraw() -> Weight { - Weight::from_ref_time(22_809_000 as u64) - .saturating_add(T::DbWeight::get().reads(6 as u64)) + Weight::from_ref_time(25_464_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + pub(crate) fn burn_asset() -> Weight { + Weight::from_ref_time(5_194_000 as u64) + } + pub(crate) fn expect_asset() -> Weight { + Weight::from_ref_time(3_698_000 as u64) + } + pub(crate) fn expect_origin() -> Weight { + Weight::from_ref_time(3_786_000 as u64) + } + pub(crate) fn expect_error() -> Weight { + Weight::from_ref_time(3_645_000 as u64) + } + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + pub(crate) fn query_pallet() -> Weight { + Weight::from_ref_time(22_993_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } + pub(crate) fn expect_pallet() -> Weight { + Weight::from_ref_time(4_043_000 as u64) + } + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + pub(crate) fn report_transact_status() -> Weight { + Weight::from_ref_time(21_668_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + pub(crate) fn clear_transact_status() -> Weight { + Weight::from_ref_time(3_673_000 as u64) + } + pub(crate) fn set_topic() -> Weight { + Weight::from_ref_time(3_661_000 as u64) + } + pub(crate) fn clear_topic() -> Weight { + Weight::from_ref_time(3_647_000 as u64) + } + pub(crate) fn set_fees_mode() -> Weight { + Weight::from_ref_time(3_599_000 as u64) + } + pub(crate) fn unpaid_execution() -> Weight { + Weight::from_ref_time(3_111_000 as u64) + } } diff --git a/runtime/kusama/src/xcm_config.rs b/runtime/kusama/src/xcm_config.rs index 55a474e239f6..96679c3919a4 100644 --- a/runtime/kusama/src/xcm_config.rs +++ b/runtime/kusama/src/xcm_config.rs @@ -17,33 +17,44 @@ //! XCM configurations for the Kusama runtime. use super::{ - parachains_origin, AccountId, Balances, CouncilCollective, Fellows, ParaId, Runtime, - RuntimeCall, RuntimeEvent, RuntimeOrigin, StakingAdmin, WeightToFee, XcmPallet, + parachains_origin, AccountId, AllPalletsWithSystem, Balances, CouncilCollective, Fellows, + ParaId, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, StakingAdmin, WeightToFee, + XcmPallet, +}; +use frame_support::{ + match_types, parameter_types, + traits::{Contains, Everything, Nothing}, + weights::Weight, }; -use frame_support::{match_types, parameter_types, traits::Everything}; use runtime_common::{xcm_sender, ToAuthor}; +use sp_core::ConstU32; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, - AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, BackingToPlurality, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, BackingToPlurality, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedWeightBounds, IsChildSystemParachain, IsConcrete, - LocationInverter, OriginToPluralityVoice, SignedAccountId32AsNative, SignedToAccountId32, + MintLocation, OriginToPluralityVoice, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, WeightInfoBounds, + WithComputedOrigin, }; +use xcm_executor::traits::WithOriginFilter; parameter_types! { /// The location of the KSM token, from the context of this chain. Since this token is native to this /// chain, we make it synonymous with it and thus it is the `Here` location, which means "equivalent to /// the context". - pub const KsmLocation: MultiLocation = Here.into(); + pub const TokenLocation: MultiLocation = Here.into_location(); /// The Kusama network ID. This is named. - pub const KusamaNetwork: NetworkId = NetworkId::Kusama; - /// Our XCM location ancestry - i.e. what, if anything, `Parent` means evaluated in our context. Since - /// Kusama is a top-level relay-chain, there is no ancestry. - pub const Ancestry: MultiLocation = Here.into(); + pub const ThisNetwork: NetworkId = Kusama; + /// Our XCM location ancestry - i.e. our location within the Consensus Universe. + /// + /// Since Kusama is a top-level relay-chain with its own consensus, it's just our network ID. + pub UniversalLocation: InteriorMultiLocation = ThisNetwork::get().into(); /// The check account, which holds any native assets that have been teleported out and not back in (yet). pub CheckAccount: AccountId = XcmPallet::check_account(); + /// The check account that is allowed to mint assets locally. + pub LocalCheckAccount: (AccountId, MintLocation) = (CheckAccount::get(), MintLocation::Local); } /// The canonical means of converting a `MultiLocation` into an `AccountId`, used when we want to determine @@ -52,41 +63,41 @@ pub type SovereignAccountOf = ( // We can convert a child parachain using the standard `AccountId` conversion. ChildParachainConvertsVia, // We can directly alias an `AccountId32` into a local account. - AccountId32Aliases, + AccountId32Aliases, ); /// Our asset transactor. This is what allows us to interest with the runtime facilities from the point of /// view of XCM-only concepts like `MultiLocation` and `MultiAsset`. /// -/// Ours is only aware of the Balances pallet, which is mapped to `KsmLocation`. +/// Ours is only aware of the Balances pallet, which is mapped to `TokenLocation`. pub type LocalAssetTransactor = XcmCurrencyAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, + IsConcrete, // We can convert the MultiLocations with our converter above: SovereignAccountOf, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, // We track our teleports in/out to keep total issuance correct. - CheckAccount, + LocalCheckAccount, >; -/// The means that we convert an the XCM message origin location into a local dispatch origin. +/// The means that we convert the XCM message origin location into a local dispatch origin. type LocalOriginConverter = ( // A `Signed` origin of the sovereign account that the original location controls. SovereignSignedViaLocation, // A child parachain, natively expressed, has the `Parachain` origin. ChildParachainAsNative, // The AccountId32 location type can be expressed natively as a `Signed` origin. - SignedAccountId32AsNative, + SignedAccountId32AsNative, // A system child parachain, expressed as a Superuser, converts to the `Root` origin. ChildSystemParachainAsSuperuser, ); parameter_types! { /// The amount of weight an XCM operation takes. This is a safe overestimate. - pub const BaseXcmWeight: u64 = 1_000_000_000; + pub const BaseXcmWeight: Weight = Weight::from_parts(1_000_000_000, 64 * 1024); /// Maximum number of instructions in a single XCM fragment. A sanity check against weight /// calculations getting too crazy. pub const MaxInstructions: u32 = 100; @@ -96,18 +107,19 @@ parameter_types! { /// individual routers. pub type XcmRouter = ( // Only one router so far - use DMP to communicate with child parachains. - xcm_sender::ChildParachainRouter, + xcm_sender::ChildParachainRouter, ); parameter_types! { - pub const Kusama: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete(KsmLocation::get()) }); - pub const Statemine: MultiLocation = Parachain(1000).into(); - pub const Encointer: MultiLocation = Parachain(1001).into(); - pub const KusamaForStatemine: (MultiAssetFilter, MultiLocation) = (Kusama::get(), Statemine::get()); - pub const KusamaForEncointer: (MultiAssetFilter, MultiLocation) = (Kusama::get(), Encointer::get()); + pub const Ksm: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete(TokenLocation::get()) }); + pub const Statemine: MultiLocation = Parachain(1000).into_location(); + pub const Encointer: MultiLocation = Parachain(1001).into_location(); + pub const KsmForStatemine: (MultiAssetFilter, MultiLocation) = (Ksm::get(), Statemine::get()); + pub const KsmForEncointer: (MultiAssetFilter, MultiLocation) = (Ksm::get(), Encointer::get()); + pub const MaxAssetsIntoHolding: u32 = 64; } pub type TrustedTeleporters = - (xcm_builder::Case, xcm_builder::Case); + (xcm_builder::Case, xcm_builder::Case); match_types! { pub type OnlyParachains: impl Contains = { @@ -119,16 +131,209 @@ match_types! { pub type Barrier = ( // Weight that is paid for may be consumed. TakeWeightCredit, - // If the message is one that immediately attemps to pay for execution, then allow it. - AllowTopLevelPaidExecutionFrom, - // Messages coming from system parachains need not pay for execution. - AllowUnpaidExecutionFrom>, // Expected responses are OK. AllowKnownQueryResponses, - // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, + WithComputedOrigin< + ( + // If the message is one that immediately attempts to pay for execution, then allow it. + AllowTopLevelPaidExecutionFrom, + // Messages coming from system parachains need not pay for execution. + AllowExplicitUnpaidExecutionFrom>, + // Subscriptions for version tracking are OK. + AllowSubscriptionsFrom, + ), + UniversalLocation, + ConstU32<8>, + >, ); +/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly +/// account for proof size weights. +/// +/// Calls that are allowed through this filter must: +/// 1. Have a fixed weight; +/// 2. Cannot lead to another call being made; +/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. +pub struct SafeCallFilter; +impl Contains for SafeCallFilter { + fn contains(call: &RuntimeCall) -> bool { + #[cfg(feature = "runtime-benchmarks")] + { + if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { + return true + } + } + + match call { + RuntimeCall::System( + frame_system::Call::kill_prefix { .. } | frame_system::Call::set_heap_pages { .. }, + ) | + RuntimeCall::Babe(..) | + RuntimeCall::Timestamp(..) | + RuntimeCall::Indices(..) | + RuntimeCall::Balances(..) | + RuntimeCall::Staking( + pallet_staking::Call::bond { .. } | + pallet_staking::Call::bond_extra { .. } | + pallet_staking::Call::unbond { .. } | + pallet_staking::Call::withdraw_unbonded { .. } | + pallet_staking::Call::validate { .. } | + pallet_staking::Call::nominate { .. } | + pallet_staking::Call::chill { .. } | + pallet_staking::Call::set_payee { .. } | + pallet_staking::Call::set_controller { .. } | + pallet_staking::Call::set_validator_count { .. } | + pallet_staking::Call::increase_validator_count { .. } | + pallet_staking::Call::scale_validator_count { .. } | + pallet_staking::Call::force_no_eras { .. } | + pallet_staking::Call::force_new_era { .. } | + pallet_staking::Call::set_invulnerables { .. } | + pallet_staking::Call::force_unstake { .. } | + pallet_staking::Call::force_new_era_always { .. } | + pallet_staking::Call::payout_stakers { .. } | + pallet_staking::Call::rebond { .. } | + pallet_staking::Call::reap_stash { .. } | + pallet_staking::Call::set_staking_configs { .. } | + pallet_staking::Call::chill_other { .. } | + pallet_staking::Call::force_apply_min_commission { .. }, + ) | + RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | + RuntimeCall::Grandpa(..) | + RuntimeCall::ImOnline(..) | + RuntimeCall::Democracy( + pallet_democracy::Call::second { .. } | + pallet_democracy::Call::vote { .. } | + pallet_democracy::Call::emergency_cancel { .. } | + pallet_democracy::Call::fast_track { .. } | + pallet_democracy::Call::veto_external { .. } | + pallet_democracy::Call::cancel_referendum { .. } | + pallet_democracy::Call::delegate { .. } | + pallet_democracy::Call::undelegate { .. } | + pallet_democracy::Call::clear_public_proposals { .. } | + pallet_democracy::Call::unlock { .. } | + pallet_democracy::Call::remove_vote { .. } | + pallet_democracy::Call::remove_other_vote { .. } | + pallet_democracy::Call::blacklist { .. } | + pallet_democracy::Call::cancel_proposal { .. }, + ) | + RuntimeCall::Council( + pallet_collective::Call::vote { .. } | + pallet_collective::Call::close_old_weight { .. } | + pallet_collective::Call::disapprove_proposal { .. } | + pallet_collective::Call::close { .. }, + ) | + RuntimeCall::TechnicalCommittee( + pallet_collective::Call::vote { .. } | + pallet_collective::Call::close_old_weight { .. } | + pallet_collective::Call::disapprove_proposal { .. } | + pallet_collective::Call::close { .. }, + ) | + RuntimeCall::PhragmenElection( + pallet_elections_phragmen::Call::remove_voter { .. } | + pallet_elections_phragmen::Call::submit_candidacy { .. } | + pallet_elections_phragmen::Call::renounce_candidacy { .. } | + pallet_elections_phragmen::Call::remove_member { .. } | + pallet_elections_phragmen::Call::clean_defunct_voters { .. }, + ) | + RuntimeCall::TechnicalMembership( + pallet_membership::Call::add_member { .. } | + pallet_membership::Call::remove_member { .. } | + pallet_membership::Call::swap_member { .. } | + pallet_membership::Call::change_key { .. } | + pallet_membership::Call::set_prime { .. } | + pallet_membership::Call::clear_prime { .. }, + ) | + RuntimeCall::Treasury(..) | + RuntimeCall::ConvictionVoting(..) | + RuntimeCall::Referenda( + pallet_referenda::Call::place_decision_deposit { .. } | + pallet_referenda::Call::refund_decision_deposit { .. } | + pallet_referenda::Call::cancel { .. } | + pallet_referenda::Call::kill { .. } | + pallet_referenda::Call::nudge_referendum { .. } | + pallet_referenda::Call::one_fewer_deciding { .. }, + ) | + RuntimeCall::FellowshipCollective(..) | + RuntimeCall::FellowshipReferenda( + pallet_referenda::Call::place_decision_deposit { .. } | + pallet_referenda::Call::refund_decision_deposit { .. } | + pallet_referenda::Call::cancel { .. } | + pallet_referenda::Call::kill { .. } | + pallet_referenda::Call::nudge_referendum { .. } | + pallet_referenda::Call::one_fewer_deciding { .. }, + ) | + RuntimeCall::Claims( + super::claims::Call::claim { .. } | + super::claims::Call::mint_claim { .. } | + super::claims::Call::move_claim { .. }, + ) | + RuntimeCall::Utility(pallet_utility::Call::as_derivative { .. }) | + RuntimeCall::Identity( + pallet_identity::Call::add_registrar { .. } | + pallet_identity::Call::set_identity { .. } | + pallet_identity::Call::clear_identity { .. } | + pallet_identity::Call::request_judgement { .. } | + pallet_identity::Call::cancel_request { .. } | + pallet_identity::Call::set_fee { .. } | + pallet_identity::Call::set_account_id { .. } | + pallet_identity::Call::set_fields { .. } | + pallet_identity::Call::provide_judgement { .. } | + pallet_identity::Call::kill_identity { .. } | + pallet_identity::Call::add_sub { .. } | + pallet_identity::Call::rename_sub { .. } | + pallet_identity::Call::remove_sub { .. } | + pallet_identity::Call::quit_sub { .. }, + ) | + RuntimeCall::Society( + pallet_society::Call::bid { .. } | + pallet_society::Call::unbid { .. } | + pallet_society::Call::vouch { .. } | + pallet_society::Call::unvouch { .. } | + pallet_society::Call::vote { .. } | + pallet_society::Call::defender_vote { .. } | + pallet_society::Call::payout { .. } | + pallet_society::Call::unfound { .. } | + pallet_society::Call::judge_suspended_member { .. } | + pallet_society::Call::judge_suspended_candidate { .. } | + pallet_society::Call::set_max_members { .. }, + ) | + RuntimeCall::Recovery(..) | + RuntimeCall::Vesting(..) | + RuntimeCall::Bounties( + pallet_bounties::Call::propose_bounty { .. } | + pallet_bounties::Call::approve_bounty { .. } | + pallet_bounties::Call::propose_curator { .. } | + pallet_bounties::Call::unassign_curator { .. } | + pallet_bounties::Call::accept_curator { .. } | + pallet_bounties::Call::award_bounty { .. } | + pallet_bounties::Call::claim_bounty { .. } | + pallet_bounties::Call::close_bounty { .. }, + ) | + RuntimeCall::ChildBounties(..) | + RuntimeCall::ElectionProviderMultiPhase(..) | + RuntimeCall::VoterList(..) | + RuntimeCall::NominationPools( + pallet_nomination_pools::Call::join { .. } | + pallet_nomination_pools::Call::bond_extra { .. } | + pallet_nomination_pools::Call::claim_payout { .. } | + pallet_nomination_pools::Call::unbond { .. } | + pallet_nomination_pools::Call::pool_withdraw_unbonded { .. } | + pallet_nomination_pools::Call::withdraw_unbonded { .. } | + pallet_nomination_pools::Call::create { .. } | + pallet_nomination_pools::Call::create_with_pool_id { .. } | + pallet_nomination_pools::Call::set_state { .. } | + pallet_nomination_pools::Call::set_configs { .. } | + pallet_nomination_pools::Call::update_roles { .. } | + pallet_nomination_pools::Call::chill { .. }, + ) | + RuntimeCall::XcmPallet(pallet_xcm::Call::limited_reserve_transfer_assets { + .. + }) => true, + _ => false, + } + } +} + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -137,7 +342,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = LocalOriginConverter; type IsReserve = (); type IsTeleporter = TrustedTeleporters; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = WeightInfoBounds< crate::weights::xcm::KusamaXcmWeight, @@ -145,11 +350,22 @@ impl xcm_executor::Config for XcmConfig { MaxInstructions, >; // The weight trader piggybacks on the existing transaction-fee conversion logic. - type Trader = UsingComponents>; + type Trader = + UsingComponents>; type ResponseHandler = XcmPallet; type AssetTrap = XcmPallet; + type AssetLocker = (); + type AssetExchanger = (); type AssetClaims = XcmPallet; type SubscriptionService = XcmPallet; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + // No bridges yet... + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = WithOriginFilter; + type SafeCallFilter = SafeCallFilter; } parameter_types! { @@ -160,6 +376,11 @@ parameter_types! { pub const FellowsBodyId: BodyId = BodyId::Technical; } +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Parachain(1000).into()); +} + /// Type to convert the council origin to a Plurality `MultiLocation` value. pub type CouncilToPlurality = BackingToPlurality< RuntimeOrigin, @@ -174,7 +395,7 @@ pub type LocalOriginToLocation = ( // `Unit` body. CouncilToPlurality, // And a usual Signed origin to be used in XCM as a corresponding AccountId32 - SignedToAccountId32, + SignedToAccountId32, ); /// Type to convert the `StakingAdmin` origin to a Plurality `MultiLocation` value. @@ -214,9 +435,17 @@ impl pallet_xcm::Config for Runtime { // transfer. type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = SovereignAccountOf; + type MaxLockers = ConstU32<8>; + type WeightInfo = crate::weights::pallet_xcm::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; } diff --git a/runtime/parachains/src/configuration/migration.rs b/runtime/parachains/src/configuration/migration.rs index 6445e9ec292c..abef3c0ccc11 100644 --- a/runtime/parachains/src/configuration/migration.rs +++ b/runtime/parachains/src/configuration/migration.rs @@ -227,7 +227,6 @@ ump_max_individual_weight : pre.ump_max_individual_weight, pvf_checking_enabled : pre.pvf_checking_enabled, pvf_voting_ttl : pre.pvf_voting_ttl, minimum_validation_upgrade_delay : pre.minimum_validation_upgrade_delay, - } }; diff --git a/runtime/parachains/src/configuration/tests.rs b/runtime/parachains/src/configuration/tests.rs index aedeacd81e2f..52ce3929dadb 100644 --- a/runtime/parachains/src/configuration/tests.rs +++ b/runtime/parachains/src/configuration/tests.rs @@ -318,7 +318,7 @@ fn setting_pending_config_members() { max_upward_queue_count: 1337, max_upward_queue_size: 228, max_downward_message_size: 2048, - ump_service_total_weight: Weight::from_ref_time(20000), + ump_service_total_weight: Weight::from_parts(20000, 20000), max_upward_message_size: 448, max_upward_message_num_per_candidate: 5, hrmp_sender_deposit: 22, @@ -331,7 +331,7 @@ fn setting_pending_config_members() { hrmp_max_parachain_outbound_channels: 10, hrmp_max_parathread_outbound_channels: 20, hrmp_max_message_num_per_candidate: 20, - ump_max_individual_weight: Weight::from_ref_time(909), + ump_max_individual_weight: Weight::from_parts(909, 909), pvf_checking_enabled: true, pvf_voting_ttl: 3, minimum_validation_upgrade_delay: 20, diff --git a/runtime/parachains/src/disputes.rs b/runtime/parachains/src/disputes.rs index d5266f5d0ac7..5e6a257344a5 100644 --- a/runtime/parachains/src/disputes.rs +++ b/runtime/parachains/src/disputes.rs @@ -973,7 +973,7 @@ impl Pallet { /// Handle a set of dispute statements corresponding to a single candidate. /// - /// Fails if the dispute data is invalid. Returns a boolean indicating whether the + /// Fails if the dispute data is invalid. Returns a Boolean indicating whether the /// dispute is fresh. fn process_checked_dispute_data( set: CheckedDisputeStatementSet, diff --git a/runtime/parachains/src/dmp.rs b/runtime/parachains/src/dmp.rs index 8b2bb85a37ba..855ae1bd85b6 100644 --- a/runtime/parachains/src/dmp.rs +++ b/runtime/parachains/src/dmp.rs @@ -29,6 +29,8 @@ pub use pallet::*; #[cfg(test)] mod tests; +pub const MAX_MESSAGE_QUEUE_SIZE: usize = 1024; + /// An error sending a downward message. #[cfg_attr(test, derive(Debug))] pub enum QueueDownwardMessageError { @@ -137,6 +139,28 @@ impl Pallet { ::DownwardMessageQueueHeads::remove(outgoing_para); } + /// Determine whether enqueuing a downward message to a specific recipient para would result + /// in an error. If this returns `Ok(())` the caller can be certain that a call to + /// `queue_downward_message` with the same parameters will be successful. + pub fn can_queue_downward_message( + config: &HostConfiguration, + para: &ParaId, + msg: &DownwardMessage, + ) -> Result<(), QueueDownwardMessageError> { + let serialized_len = msg.len() as u32; + if serialized_len > config.max_downward_message_size { + return Err(QueueDownwardMessageError::ExceedsMaxMessageSize) + } + + if ::DownwardMessageQueues::decode_len(para).unwrap_or(0) > + MAX_MESSAGE_QUEUE_SIZE + { + return Err(QueueDownwardMessageError::ExceedsMaxMessageSize) + } + + Ok(()) + } + /// Enqueue a downward message to a specific recipient para. /// /// When encoded, the message should not exceed the `config.max_downward_message_size`. @@ -155,6 +179,12 @@ impl Pallet { return Err(QueueDownwardMessageError::ExceedsMaxMessageSize) } + if ::DownwardMessageQueues::decode_len(para).unwrap_or(0) > + MAX_MESSAGE_QUEUE_SIZE + { + return Err(QueueDownwardMessageError::ExceedsMaxMessageSize) + } + let inbound = InboundDownwardMessage { msg, sent_at: >::block_number() }; diff --git a/runtime/parachains/src/mock.rs b/runtime/parachains/src/mock.rs index 0f0f62e3bb00..08d0b05c3d99 100644 --- a/runtime/parachains/src/mock.rs +++ b/runtime/parachains/src/mock.rs @@ -83,7 +83,7 @@ parameter_types! { pub const BlockHashCount: u32 = 250; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max( - Weight::from_ref_time(4 * 1024 * 1024).set_proof_size(u64::MAX), + Weight::from_parts(4 * 1024 * 1024, u64::MAX), ); } @@ -394,7 +394,7 @@ impl UmpSink for TestUmpSink { max_weight: Weight, ) -> Result { let weight = match u32::decode(&mut &actual_msg[..]) { - Ok(w) => Weight::from_ref_time(w as u64), + Ok(w) => Weight::from_parts(w as u64, w as u64), Err(_) => return Ok(Weight::zero()), // same as the real `UmpSink` }; if weight.any_gt(max_weight) { diff --git a/runtime/parachains/src/paras/mod.rs b/runtime/parachains/src/paras/mod.rs index f372a88713e6..e5297bd64f6c 100644 --- a/runtime/parachains/src/paras/mod.rs +++ b/runtime/parachains/src/paras/mod.rs @@ -159,7 +159,7 @@ pub struct ReplacementTimes { #[cfg_attr(test, derive(Debug, Clone, PartialEq))] pub struct ParaPastCodeMeta { /// Block numbers where the code was expected to be replaced and where the code - /// was actually replaced, respectively. The first is used to do accurate lookups + /// was actually replaced, respectively. The first is used to do accurate look-ups /// of historic code in historic contexts, whereas the second is used to do /// pruning on an accurate timeframe. These can be used as indices /// into the `PastCodeHash` map along with the `ParaId` to fetch the code itself. @@ -520,7 +520,7 @@ impl WeightInfo for TestWeightInfo { } fn include_pvf_check_statement() -> Weight { // This special value is to distinguish from the finalizing variants above in tests. - Weight::MAX - Weight::from_ref_time(1) + Weight::MAX - Weight::from_parts(1, 1) } } diff --git a/runtime/parachains/src/ump.rs b/runtime/parachains/src/ump.rs index b0acefbc6065..6a5f8b96b4cf 100644 --- a/runtime/parachains/src/ump.rs +++ b/runtime/parachains/src/ump.rs @@ -31,9 +31,12 @@ pub use pallet::*; /// This is used for benchmarking sanely bounding relevant storate items. It is expected from the `configurations` /// pallet to check these values before setting. pub const MAX_UPWARD_MESSAGE_SIZE_BOUND: u32 = 50 * 1024; +/// Maximum amount of overweight messages that can exist in the queue at any given time. +pub const MAX_OVERWEIGHT_MESSAGES: u32 = 1000; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; +pub mod migration; #[cfg(test)] pub(crate) mod tests; @@ -133,13 +136,11 @@ impl, C: Config> UmpSink }, Ok((Ok(xcm_message), weight_used)) => { let xcm_junction = Junction::Parachain(origin.into()); - let outcome = - XcmExecutor::execute_xcm(xcm_junction, xcm_message, max_weight.ref_time()); + let outcome = XcmExecutor::execute_xcm(xcm_junction, xcm_message, id, max_weight); match outcome { - Outcome::Error(XcmError::WeightLimitReached(required)) => - Err((id, Weight::from_ref_time(required))), + Outcome::Error(XcmError::WeightLimitReached(required)) => Err((id, required)), outcome => { - let outcome_weight = Weight::from_ref_time(outcome.weight_used()); + let outcome_weight = outcome.weight_used(); Pallet::::deposit_event(Event::ExecutedUpward(id, outcome)); Ok(weight_used.saturating_add(outcome_weight)) }, @@ -215,6 +216,7 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::without_storage_info] + #[pallet::storage_version(migration::STORAGE_VERSION)] pub struct Pallet(_); #[pallet::config] @@ -328,7 +330,7 @@ pub mod pallet { /// These messages stay there until manually dispatched. #[pallet::storage] pub type Overweight = - StorageMap<_, Twox64Concat, OverweightIndex, (ParaId, Vec), OptionQuery>; + CountedStorageMap<_, Twox64Concat, OverweightIndex, (ParaId, Vec), OptionQuery>; /// The number of overweight messages ever recorded in `Overweight` (and thus the lowest free /// index). @@ -508,6 +510,8 @@ impl Pallet { /// Devote some time into dispatching pending upward messages. pub(crate) fn process_pending_upward_messages() -> Weight { + const MAX_MESSAGES_PER_BLOCK: u8 = 10; + let mut messages_processed = 0; let mut weight_used = Weight::zero(); let config = >::config(); @@ -515,7 +519,12 @@ impl Pallet { let mut queue_cache = QueueCache::new(); while let Some(dispatchee) = cursor.peek() { - if weight_used.any_gte(config.ump_service_total_weight) { + if weight_used.any_gte(config.ump_service_total_weight) || + messages_processed >= MAX_MESSAGES_PER_BLOCK + { + // Temporarily allow for processing of a max of 10 messages per block, until we + // properly account for proof size weights. + // // Then check whether we've reached or overshoot the // preferred weight for the dispatching stage. // @@ -537,13 +546,16 @@ impl Pallet { // our remaining weight limit, then consume it. let maybe_next = queue_cache.peek_front::(dispatchee); if let Some(upward_message) = maybe_next { + messages_processed += 1; match T::UmpSink::process_upward_message(dispatchee, upward_message, max_weight) { Ok(used) => { weight_used += used; let _ = queue_cache.consume_front::(dispatchee); }, Err((id, required)) => { - if required.any_gt(config.ump_max_individual_weight) { + let is_under_limit = Overweight::::count() < MAX_OVERWEIGHT_MESSAGES; + weight_used.saturating_accrue(T::DbWeight::get().reads(1)); + if required.any_gt(config.ump_max_individual_weight) && is_under_limit { // overweight - add to overweight queue and continue with message // execution consuming the message. let upward_message = queue_cache.consume_front::(dispatchee).expect( diff --git a/runtime/parachains/src/ump/benchmarking.rs b/runtime/parachains/src/ump/benchmarking.rs index a257da79b373..91f3d4ac7ec2 100644 --- a/runtime/parachains/src/ump/benchmarking.rs +++ b/runtime/parachains/src/ump/benchmarking.rs @@ -42,8 +42,8 @@ fn queue_upward_msg( fn create_message_min_size(size: u32) -> Vec { // Create a message with an empty remark call to determine the encoding overhead let msg_size_empty_transact = VersionedXcm::::from(Xcm::(vec![Transact { - origin_type: OriginKind::SovereignAccount, - require_weight_at_most: Weight::MAX.ref_time(), + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::MAX, call: frame_system::Call::::remark_with_event { remark: vec![] }.encode().into(), }])) .encode() @@ -54,8 +54,8 @@ fn create_message_min_size(size: u32) -> Vec { let mut remark = Vec::new(); remark.resize(size, 0u8); let msg = VersionedXcm::::from(Xcm::(vec![Transact { - origin_type: OriginKind::SovereignAccount, - require_weight_at_most: Weight::MAX.ref_time(), + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::MAX, call: frame_system::Call::::remark_with_event { remark }.encode().into(), }])) .encode(); @@ -65,12 +65,11 @@ fn create_message_min_size(size: u32) -> Vec { } fn create_message_overweight() -> Vec { - let max_block_weight = T::BlockWeights::get().max_block; // We use a `set_code` Call because it let call = frame_system::Call::::set_code { code: vec![] }; VersionedXcm::::from(Xcm::(vec![Transact { - origin_type: OriginKind::Superuser, - require_weight_at_most: max_block_weight.ref_time(), + origin_kind: OriginKind::Superuser, + require_weight_at_most: Weight::MAX / 2, call: call.encode().into(), }])) .encode() @@ -107,7 +106,6 @@ frame_benchmarking::benchmarks! { service_overweight { let host_conf = configuration::ActiveConfig::::get(); - let weight = host_conf.ump_max_individual_weight + host_conf.ump_max_individual_weight + Weight::from_ref_time(1000000); let para = ParaId::from(1978); // The message's weight does not really matter here, as we add service_overweight's // max_weight parameter to the extrinsic's weight in the weight calculation. @@ -117,7 +115,7 @@ frame_benchmarking::benchmarks! { let msg = create_message_overweight::(); // This just makes sure that 0 is not a valid index and we can use it later on. - let _ = Ump::::service_overweight(RawOrigin::Root.into(), 0, Weight::from_ref_time(1000).set_proof_size(u64::MAX)); + let _ = Ump::::service_overweight(RawOrigin::Root.into(), 0, Weight::from_parts(1000, 1000)); // Start with the block number 1. This is needed because should an event be // emitted during the genesis block they will be implicitly wiped. frame_system::Pallet::::set_block_number(1u32.into()); diff --git a/runtime/parachains/src/ump/migration.rs b/runtime/parachains/src/ump/migration.rs new file mode 100644 index 000000000000..666d224b4b06 --- /dev/null +++ b/runtime/parachains/src/ump/migration.rs @@ -0,0 +1,49 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use crate::ump::{Config, Overweight, Pallet}; +use frame_support::{ + pallet_prelude::*, + traits::{OnRuntimeUpgrade, StorageVersion}, + weights::Weight, +}; + +pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + +pub mod v1 { + use super::*; + + pub struct MigrateToV1(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV1 { + fn on_runtime_upgrade() -> Weight { + if StorageVersion::get::>() == 0 { + let mut weight = T::DbWeight::get().reads(1); + + let overweight_messages = Overweight::::initialize_counter() as u64; + log::info!("Initialized Overweight to {}", overweight_messages); + + weight.saturating_accrue(T::DbWeight::get().reads_writes(overweight_messages, 1)); + + StorageVersion::new(1).put::>(); + + weight.saturating_add(T::DbWeight::get().writes(1)) + } else { + log::warn!("skipping v1, should be removed"); + T::DbWeight::get().reads(1) + } + } + } +} diff --git a/runtime/parachains/src/ump/tests.rs b/runtime/parachains/src/ump/tests.rs index 5113570e06a5..37e166d811a1 100644 --- a/runtime/parachains/src/ump/tests.rs +++ b/runtime/parachains/src/ump/tests.rs @@ -34,12 +34,12 @@ pub(super) struct GenesisConfigBuilder { impl Default for GenesisConfigBuilder { fn default() -> Self { Self { - max_upward_message_size: 16, + max_upward_message_size: 32, max_upward_message_num_per_candidate: 2, max_upward_queue_count: 4, max_upward_queue_size: 64, - ump_service_total_weight: Weight::from_ref_time(1000).set_proof_size(1000), - ump_max_individual_weight: Weight::from_ref_time(100).set_proof_size(100), + ump_service_total_weight: Weight::from_parts(1000, 1000), + ump_max_individual_weight: Weight::from_parts(100, 100), } } } @@ -156,7 +156,7 @@ fn dispatch_resume_after_exceeding_dispatch_stage_weight() { new_test_ext( GenesisConfigBuilder { - ump_service_total_weight: Weight::from_ref_time(500).set_proof_size(500), + ump_service_total_weight: Weight::from_parts(500, 500), ..Default::default() } .build(), @@ -203,8 +203,8 @@ fn dispatch_keeps_message_after_weight_exhausted() { new_test_ext( GenesisConfigBuilder { - ump_service_total_weight: Weight::from_ref_time(500).set_proof_size(500), - ump_max_individual_weight: Weight::from_ref_time(300).set_proof_size(300), + ump_service_total_weight: Weight::from_parts(500, 500), + ump_max_individual_weight: Weight::from_parts(300, 300), ..Default::default() } .build(), @@ -243,7 +243,7 @@ fn dispatch_correctly_handle_remove_of_latest() { new_test_ext( GenesisConfigBuilder { - ump_service_total_weight: Weight::from_ref_time(900).set_proof_size(900), + ump_service_total_weight: Weight::from_parts(900, 900), ..Default::default() } .build(), @@ -296,7 +296,7 @@ fn service_overweight_unknown() { // the next test. new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { assert_noop!( - Ump::service_overweight(RuntimeOrigin::root(), 0, Weight::from_ref_time(1000)), + Ump::service_overweight(RuntimeOrigin::root(), 0, Weight::from_parts(1000, 1000)), Error::::UnknownMessageIndex ); }); @@ -312,8 +312,8 @@ fn overweight_queue_works() { new_test_ext( GenesisConfigBuilder { - ump_service_total_weight: Weight::from_ref_time(900).set_proof_size(900), - ump_max_individual_weight: Weight::from_ref_time(300).set_proof_size(300), + ump_service_total_weight: Weight::from_parts(900, 900), + ump_max_individual_weight: Weight::from_parts(300, 300), ..Default::default() } .build(), @@ -338,7 +338,7 @@ fn overweight_queue_works() { para_a, upward_message_id(&a_msg_3[..]), 0, - Weight::from_ref_time(500), + Weight::from_parts(500, 500), ) .into(), ); @@ -346,18 +346,18 @@ fn overweight_queue_works() { // Now verify that if we wanted to service this overweight message with less than enough // weight it will fail. assert_noop!( - Ump::service_overweight(RuntimeOrigin::root(), 0, Weight::from_ref_time(499)), + Ump::service_overweight(RuntimeOrigin::root(), 0, Weight::from_parts(499, 499)), Error::::WeightOverLimit ); // ... and if we try to service it with just enough weight it will succeed as well. - assert_ok!(Ump::service_overweight(RuntimeOrigin::root(), 0, Weight::from_ref_time(500))); - assert_last_event(Event::OverweightServiced(0, Weight::from_ref_time(500)).into()); + assert_ok!(Ump::service_overweight(RuntimeOrigin::root(), 0, Weight::from_parts(500, 500))); + assert_last_event(Event::OverweightServiced(0, Weight::from_parts(500, 500)).into()); // ... and if we try to service a message with index that doesn't exist it will error // out. assert_noop!( - Ump::service_overweight(RuntimeOrigin::root(), 1, Weight::from_ref_time(1000)), + Ump::service_overweight(RuntimeOrigin::root(), 1, Weight::from_parts(1000, 1000)), Error::::UnknownMessageIndex ); }); diff --git a/runtime/polkadot/src/lib.rs b/runtime/polkadot/src/lib.rs index 3a1c211bf028..086f713ae0db 100644 --- a/runtime/polkadot/src/lib.rs +++ b/runtime/polkadot/src/lib.rs @@ -1303,7 +1303,7 @@ impl parachains_ump::Config for Runtime { crate::parachains_ump::XcmSink, Runtime>; type FirstMessageFactorPercent = FirstMessageFactorPercent; type ExecuteOverweightOrigin = EnsureRoot; - type WeightInfo = parachains_ump::TestWeightInfo; + type WeightInfo = weights::runtime_parachains_ump::WeightInfo; } impl parachains_dmp::Config for Runtime {} @@ -1609,6 +1609,9 @@ pub type Migrations = ( pallet_staking::migrations::v13::MigrateToV13, parachains_disputes::migration::v1::MigrateToV1, parachains_configuration::migration::v4::MigrateToV4, + // "Use 2D weights in XCM v3" + pallet_xcm::migration::v1::MigrateToV1, + parachains_ump::migration::v1::MigrateToV1, ); /// Unchecked extrinsic type as expected by this runtime. @@ -1680,6 +1683,8 @@ mod benches { [pallet_treasury, Treasury] [pallet_utility, Utility] [pallet_vesting, Vesting] + // XCM + [pallet_xcm, XcmPallet] ); } diff --git a/runtime/polkadot/src/weights/mod.rs b/runtime/polkadot/src/weights/mod.rs index cef1ce83bd11..cef8d5d00aa0 100644 --- a/runtime/polkadot/src/weights/mod.rs +++ b/runtime/polkadot/src/weights/mod.rs @@ -43,6 +43,7 @@ pub mod pallet_tips; pub mod pallet_treasury; pub mod pallet_utility; pub mod pallet_vesting; +pub mod pallet_xcm; pub mod runtime_common_auctions; pub mod runtime_common_claims; pub mod runtime_common_crowdloan; @@ -54,3 +55,4 @@ pub mod runtime_parachains_hrmp; pub mod runtime_parachains_initializer; pub mod runtime_parachains_paras; pub mod runtime_parachains_paras_inherent; +pub mod runtime_parachains_ump; diff --git a/runtime/polkadot/src/weights/pallet_xcm.rs b/runtime/polkadot/src/weights/pallet_xcm.rs new file mode 100644 index 000000000000..cc1cca28068d --- /dev/null +++ b/runtime/polkadot/src/weights/pallet_xcm.rs @@ -0,0 +1,174 @@ +// Copyright 2017-2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . +//! Autogenerated weights for `pallet_xcm` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-12-16, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("polkadot-dev"), DB CACHE: 1024 + +// Executed Command: +// /home/benchbot/cargo_target_dir/production/polkadot +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/polkadot/.git/.artifacts/bench.json +// --pallet=pallet_xcm +// --chain=polkadot-dev +// --header=./file_header.txt +// --output=./runtime/polkadot/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weight functions for `pallet_xcm`. +pub struct WeightInfo(PhantomData); +impl pallet_xcm::WeightInfo for WeightInfo { + // Storage: Configuration ActiveConfig (r:1 w:0) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + fn send() -> Weight { + // Minimum execution time: 35_717 nanoseconds. + Weight::from_ref_time(36_278_000) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) + } + fn teleport_assets() -> Weight { + // Minimum execution time: 28_865 nanoseconds. + Weight::from_ref_time(29_336_000) + } + fn reserve_transfer_assets() -> Weight { + // Minimum execution time: 27_531 nanoseconds. + Weight::from_ref_time(28_248_000) + } + // Storage: Benchmark Override (r:0 w:0) + fn execute() -> Weight { + // Minimum execution time: 18_446_744_073_709_551 nanoseconds. + Weight::from_ref_time(18_446_744_073_709_551_000) + } + // Storage: XcmPallet SupportedVersion (r:0 w:1) + fn force_xcm_version() -> Weight { + // Minimum execution time: 15_205 nanoseconds. + Weight::from_ref_time(15_526_000) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: XcmPallet SafeXcmVersion (r:0 w:1) + fn force_default_xcm_version() -> Weight { + // Minimum execution time: 4_336 nanoseconds. + Weight::from_ref_time(4_518_000) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: XcmPallet VersionNotifiers (r:1 w:1) + // Storage: XcmPallet QueryCounter (r:1 w:1) + // Storage: Configuration ActiveConfig (r:1 w:0) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + // Storage: XcmPallet Queries (r:0 w:1) + fn force_subscribe_version_notify() -> Weight { + // Minimum execution time: 41_446 nanoseconds. + Weight::from_ref_time(42_152_000) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(6)) + } + // Storage: XcmPallet VersionNotifiers (r:1 w:1) + // Storage: Configuration ActiveConfig (r:1 w:0) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + // Storage: XcmPallet Queries (r:0 w:1) + fn force_unsubscribe_version_notify() -> Weight { + // Minimum execution time: 44_944 nanoseconds. + Weight::from_ref_time(45_519_000) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(5)) + } + // Storage: XcmPallet SupportedVersion (r:4 w:2) + fn migrate_supported_version() -> Weight { + // Minimum execution time: 15_254 nanoseconds. + Weight::from_ref_time(15_491_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: XcmPallet VersionNotifiers (r:4 w:2) + fn migrate_version_notifiers() -> Weight { + // Minimum execution time: 15_083 nanoseconds. + Weight::from_ref_time(15_298_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: XcmPallet VersionNotifyTargets (r:5 w:0) + fn already_notified_target() -> Weight { + // Minimum execution time: 17_889 nanoseconds. + Weight::from_ref_time(18_144_000) + .saturating_add(T::DbWeight::get().reads(5)) + } + // Storage: XcmPallet VersionNotifyTargets (r:2 w:1) + // Storage: Configuration ActiveConfig (r:1 w:0) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + fn notify_current_targets() -> Weight { + // Minimum execution time: 37_255 nanoseconds. + Weight::from_ref_time(37_893_000) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: XcmPallet VersionNotifyTargets (r:3 w:0) + fn notify_target_migration_fail() -> Weight { + // Minimum execution time: 7_884 nanoseconds. + Weight::from_ref_time(8_111_000) + .saturating_add(T::DbWeight::get().reads(3)) + } + // Storage: XcmPallet VersionNotifyTargets (r:4 w:2) + fn migrate_version_notify_targets() -> Weight { + // Minimum execution time: 15_853 nanoseconds. + Weight::from_ref_time(16_220_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: XcmPallet VersionNotifyTargets (r:4 w:2) + // Storage: Configuration ActiveConfig (r:1 w:0) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + fn migrate_and_notify_old_targets() -> Weight { + // Minimum execution time: 43_836 nanoseconds. + Weight::from_ref_time(44_836_000) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(5)) + } +} diff --git a/runtime/polkadot/src/weights/runtime_parachains_ump.rs b/runtime/polkadot/src/weights/runtime_parachains_ump.rs new file mode 100644 index 000000000000..8f1eb2d54749 --- /dev/null +++ b/runtime/polkadot/src/weights/runtime_parachains_ump.rs @@ -0,0 +1,74 @@ +// Copyright 2017-2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . +//! Autogenerated weights for `runtime_parachains::ump` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-01-16, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("polkadot-dev"), DB CACHE: 1024 + +// Executed Command: +// /home/benchbot/cargo_target_dir/production/polkadot +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/polkadot/.git/.artifacts/bench.json +// --pallet=runtime_parachains::ump +// --chain=polkadot-dev +// --header=./file_header.txt +// --output=./runtime/polkadot/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weight functions for `runtime_parachains::ump`. +pub struct WeightInfo(PhantomData); +impl runtime_parachains::ump::WeightInfo for WeightInfo { + /// The range of component `s` is `[0, 51200]`. + fn process_upward_message(s: u32, ) -> Weight { + // Minimum execution time: 10_291 nanoseconds. + Weight::from_ref_time(4_272_368) + // Standard Error: 12 + .saturating_add(Weight::from_ref_time(1_872).saturating_mul(s.into())) + } + // Storage: Ump NeedsDispatch (r:1 w:1) + // Storage: Ump NextDispatchRoundStartWith (r:1 w:1) + // Storage: Ump RelayDispatchQueues (r:0 w:1) + // Storage: Ump RelayDispatchQueueSize (r:0 w:1) + fn clean_ump_after_outgoing() -> Weight { + // Minimum execution time: 9_837 nanoseconds. + Weight::from_ref_time(9_951_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: Ump Overweight (r:1 w:1) + // Storage: Ump CounterForOverweight (r:1 w:1) + fn service_overweight() -> Weight { + // Minimum execution time: 29_540 nanoseconds. + Weight::from_ref_time(29_889_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/runtime/polkadot/src/xcm_config.rs b/runtime/polkadot/src/xcm_config.rs index 39f7a337479a..abdd850f9e61 100644 --- a/runtime/polkadot/src/xcm_config.rs +++ b/runtime/polkadot/src/xcm_config.rs @@ -17,35 +17,39 @@ //! XCM configuration for Polkadot. use super::{ - parachains_origin, AccountId, Balances, CouncilCollective, ParaId, Runtime, RuntimeCall, - RuntimeEvent, RuntimeOrigin, WeightToFee, XcmPallet, + parachains_origin, AccountId, AllPalletsWithSystem, Balances, CouncilCollective, ParaId, + Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmPallet, }; use frame_support::{ match_types, parameter_types, - traits::{Everything, Nothing}, + traits::{Contains, Everything, Nothing}, + weights::Weight, }; use runtime_common::{xcm_sender, ToAuthor}; +use sp_core::ConstU32; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, BackingToPlurality, ChildParachainAsNative, ChildParachainConvertsVia, CurrencyAdapter as XcmCurrencyAdapter, FixedWeightBounds, - IsConcrete, LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, + IsConcrete, MintLocation, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, WithComputedOrigin, }; +use xcm_executor::traits::WithOriginFilter; parameter_types! { /// The location of the DOT token, from the context of this chain. Since this token is native to this /// chain, we make it synonymous with it and thus it is the `Here` location, which means "equivalent to /// the context". - pub const DotLocation: MultiLocation = Here.into(); + pub const TokenLocation: MultiLocation = Here.into_location(); /// The Polkadot network ID. This is named. - pub const PolkadotNetwork: NetworkId = NetworkId::Polkadot; - /// Our XCM location ancestry - i.e. what, if anything, `Parent` means evaluated in our context. Since - /// Polkadot is a top-level relay-chain, there is no ancestry. - pub const Ancestry: MultiLocation = Here.into(); - /// The check account, which holds any native assets that have been teleported out and not back in (yet). + pub const ThisNetwork: NetworkId = NetworkId::Polkadot; + /// Our location in the universe of consensus systems. + pub const UniversalLocation: InteriorMultiLocation = X1(GlobalConsensus(ThisNetwork::get())); + /// The Checking Account, which holds any native assets that have been teleported out and not back in (yet). pub CheckAccount: AccountId = XcmPallet::check_account(); + /// The Checking Account along with the indication that the local chain is able to mint tokens. + pub LocalCheckAccount: (AccountId, MintLocation) = (CheckAccount::get(), MintLocation::Local); } /// The canonical means of converting a `MultiLocation` into an `AccountId`, used when we want to determine @@ -54,24 +58,24 @@ pub type SovereignAccountOf = ( // We can convert a child parachain using the standard `AccountId` conversion. ChildParachainConvertsVia, // We can directly alias an `AccountId32` into a local account. - AccountId32Aliases, + AccountId32Aliases, ); /// Our asset transactor. This is what allows us to interact with the runtime assets from the point of /// view of XCM-only concepts like `MultiLocation` and `MultiAsset`. /// -/// Ours is only aware of the Balances pallet, which is mapped to `DotLocation`. +/// Ours is only aware of the Balances pallet, which is mapped to `TokenLocation`. pub type LocalAssetTransactor = XcmCurrencyAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, + IsConcrete, // We can convert the MultiLocations with our converter above: SovereignAccountOf, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, // We track our teleports in/out to keep total issuance correct. - CheckAccount, + LocalCheckAccount, >; /// The means that we convert an XCM origin `MultiLocation` into the runtime's `Origin` type for @@ -86,12 +90,12 @@ type LocalOriginConverter = ( ChildParachainAsNative, // If the origin kind is `Native` and the XCM origin is the `AccountId32` location, then it can // be expressed using the `Signed` origin variant. - SignedAccountId32AsNative, + SignedAccountId32AsNative, ); parameter_types! { /// The amount of weight an XCM operation takes. This is a safe overestimate. - pub const BaseXcmWeight: u64 = 1_000_000_000; + pub const BaseXcmWeight: Weight = Weight::from_parts(1_000_000_000, 64 * 1024); /// Maximum number of instructions in a single XCM fragment. A sanity check against weight /// calculations getting too crazy. pub const MaxInstructions: u32 = 100; @@ -101,18 +105,19 @@ parameter_types! { /// individual routers. pub type XcmRouter = ( // Only one router so far - use DMP to communicate with child parachains. - xcm_sender::ChildParachainRouter, + xcm_sender::ChildParachainRouter, ); parameter_types! { - pub const Polkadot: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete(DotLocation::get()) }); - pub const PolkadotForStatemint: (MultiAssetFilter, MultiLocation) = (Polkadot::get(), Parachain(1000).into()); - pub const PolkadotForCollectives: (MultiAssetFilter, MultiLocation) = (Polkadot::get(), Parachain(1001).into()); + pub const Dot: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete(TokenLocation::get()) }); + pub const DotForStatemint: (MultiAssetFilter, MultiLocation) = (Dot::get(), Parachain(1000).into_location()); + pub const DotForCollectives: (MultiAssetFilter, MultiLocation) = (Dot::get(), Parachain(1001).into_location()); + pub const MaxAssetsIntoHolding: u32 = 64; } -/// Polkadot Relay recognizes/respects System parachains as teleporters. +/// Polkadot Relay recognizes/respects the Statemint chain as a teleporter. pub type TrustedTeleporters = - (xcm_builder::Case, xcm_builder::Case); + (xcm_builder::Case, xcm_builder::Case); match_types! { pub type OnlyParachains: impl Contains = { @@ -124,14 +129,175 @@ match_types! { pub type Barrier = ( // Weight that is paid for may be consumed. TakeWeightCredit, - // If the message is one that immediately attemps to pay for execution, then allow it. - AllowTopLevelPaidExecutionFrom, // Expected responses are OK. AllowKnownQueryResponses, - // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, + WithComputedOrigin< + ( + // If the message is one that immediately attemps to pay for execution, then allow it. + AllowTopLevelPaidExecutionFrom, + // Subscriptions for version tracking are OK. + AllowSubscriptionsFrom, + ), + UniversalLocation, + ConstU32<8>, + >, ); +/// A call filter for the XCM Transact instruction. This is a temporary measure until we +/// properly account for proof size weights. +/// +/// Calls that are allowed through this filter must: +/// 1. Have a fixed weight; +/// 2. Cannot lead to another call being made; +/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. +pub struct SafeCallFilter; +impl Contains for SafeCallFilter { + fn contains(call: &RuntimeCall) -> bool { + #[cfg(feature = "runtime-benchmarks")] + { + if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { + return true + } + } + + match call { + RuntimeCall::System( + frame_system::Call::kill_prefix { .. } | frame_system::Call::set_heap_pages { .. }, + ) | + RuntimeCall::Babe(..) | + RuntimeCall::Timestamp(..) | + RuntimeCall::Indices(..) | + RuntimeCall::Balances(..) | + RuntimeCall::Staking( + pallet_staking::Call::bond { .. } | + pallet_staking::Call::bond_extra { .. } | + pallet_staking::Call::unbond { .. } | + pallet_staking::Call::withdraw_unbonded { .. } | + pallet_staking::Call::validate { .. } | + pallet_staking::Call::nominate { .. } | + pallet_staking::Call::chill { .. } | + pallet_staking::Call::set_payee { .. } | + pallet_staking::Call::set_controller { .. } | + pallet_staking::Call::set_validator_count { .. } | + pallet_staking::Call::increase_validator_count { .. } | + pallet_staking::Call::scale_validator_count { .. } | + pallet_staking::Call::force_no_eras { .. } | + pallet_staking::Call::force_new_era { .. } | + pallet_staking::Call::set_invulnerables { .. } | + pallet_staking::Call::force_unstake { .. } | + pallet_staking::Call::force_new_era_always { .. } | + pallet_staking::Call::payout_stakers { .. } | + pallet_staking::Call::rebond { .. } | + pallet_staking::Call::reap_stash { .. } | + pallet_staking::Call::set_staking_configs { .. } | + pallet_staking::Call::chill_other { .. } | + pallet_staking::Call::force_apply_min_commission { .. }, + ) | + RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | + RuntimeCall::Grandpa(..) | + RuntimeCall::ImOnline(..) | + RuntimeCall::Democracy( + pallet_democracy::Call::second { .. } | + pallet_democracy::Call::vote { .. } | + pallet_democracy::Call::emergency_cancel { .. } | + pallet_democracy::Call::fast_track { .. } | + pallet_democracy::Call::veto_external { .. } | + pallet_democracy::Call::cancel_referendum { .. } | + pallet_democracy::Call::delegate { .. } | + pallet_democracy::Call::undelegate { .. } | + pallet_democracy::Call::clear_public_proposals { .. } | + pallet_democracy::Call::unlock { .. } | + pallet_democracy::Call::remove_vote { .. } | + pallet_democracy::Call::remove_other_vote { .. } | + pallet_democracy::Call::blacklist { .. } | + pallet_democracy::Call::cancel_proposal { .. }, + ) | + RuntimeCall::Council( + pallet_collective::Call::vote { .. } | + pallet_collective::Call::close_old_weight { .. } | + pallet_collective::Call::disapprove_proposal { .. } | + pallet_collective::Call::close { .. }, + ) | + RuntimeCall::TechnicalCommittee( + pallet_collective::Call::vote { .. } | + pallet_collective::Call::close_old_weight { .. } | + pallet_collective::Call::disapprove_proposal { .. } | + pallet_collective::Call::close { .. }, + ) | + RuntimeCall::PhragmenElection( + pallet_elections_phragmen::Call::remove_voter { .. } | + pallet_elections_phragmen::Call::submit_candidacy { .. } | + pallet_elections_phragmen::Call::renounce_candidacy { .. } | + pallet_elections_phragmen::Call::remove_member { .. } | + pallet_elections_phragmen::Call::clean_defunct_voters { .. }, + ) | + RuntimeCall::TechnicalMembership( + pallet_membership::Call::add_member { .. } | + pallet_membership::Call::remove_member { .. } | + pallet_membership::Call::swap_member { .. } | + pallet_membership::Call::change_key { .. } | + pallet_membership::Call::set_prime { .. } | + pallet_membership::Call::clear_prime { .. }, + ) | + RuntimeCall::Treasury(..) | + RuntimeCall::Claims( + super::claims::Call::claim { .. } | + super::claims::Call::mint_claim { .. } | + super::claims::Call::move_claim { .. }, + ) | + RuntimeCall::Utility(pallet_utility::Call::as_derivative { .. }) | + RuntimeCall::Identity( + pallet_identity::Call::add_registrar { .. } | + pallet_identity::Call::set_identity { .. } | + pallet_identity::Call::clear_identity { .. } | + pallet_identity::Call::request_judgement { .. } | + pallet_identity::Call::cancel_request { .. } | + pallet_identity::Call::set_fee { .. } | + pallet_identity::Call::set_account_id { .. } | + pallet_identity::Call::set_fields { .. } | + pallet_identity::Call::provide_judgement { .. } | + pallet_identity::Call::kill_identity { .. } | + pallet_identity::Call::add_sub { .. } | + pallet_identity::Call::rename_sub { .. } | + pallet_identity::Call::remove_sub { .. } | + pallet_identity::Call::quit_sub { .. }, + ) | + RuntimeCall::Vesting(..) | + RuntimeCall::Bounties( + pallet_bounties::Call::propose_bounty { .. } | + pallet_bounties::Call::approve_bounty { .. } | + pallet_bounties::Call::propose_curator { .. } | + pallet_bounties::Call::unassign_curator { .. } | + pallet_bounties::Call::accept_curator { .. } | + pallet_bounties::Call::award_bounty { .. } | + pallet_bounties::Call::claim_bounty { .. } | + pallet_bounties::Call::close_bounty { .. }, + ) | + RuntimeCall::ChildBounties(..) | + RuntimeCall::ElectionProviderMultiPhase(..) | + RuntimeCall::VoterList(..) | + RuntimeCall::NominationPools( + pallet_nomination_pools::Call::join { .. } | + pallet_nomination_pools::Call::bond_extra { .. } | + pallet_nomination_pools::Call::claim_payout { .. } | + pallet_nomination_pools::Call::unbond { .. } | + pallet_nomination_pools::Call::pool_withdraw_unbonded { .. } | + pallet_nomination_pools::Call::withdraw_unbonded { .. } | + pallet_nomination_pools::Call::create { .. } | + pallet_nomination_pools::Call::create_with_pool_id { .. } | + pallet_nomination_pools::Call::set_state { .. } | + pallet_nomination_pools::Call::set_configs { .. } | + pallet_nomination_pools::Call::update_roles { .. } | + pallet_nomination_pools::Call::chill { .. }, + ) | + RuntimeCall::XcmPallet(pallet_xcm::Call::limited_reserve_transfer_assets { + .. + }) => true, + _ => false, + } + } +} + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -141,21 +307,35 @@ impl xcm_executor::Config for XcmConfig { // Polkadot Relay recognises no chains which act as reserves. type IsReserve = (); type IsTeleporter = TrustedTeleporters; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; // The weight trader piggybacks on the existing transaction-fee conversion logic. - type Trader = UsingComponents>; + type Trader = + UsingComponents>; type ResponseHandler = XcmPallet; type AssetTrap = XcmPallet; + type AssetLocker = (); + type AssetExchanger = (); type AssetClaims = XcmPallet; type SubscriptionService = XcmPallet; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + // No bridges yet... + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = WithOriginFilter; + type SafeCallFilter = SafeCallFilter; } parameter_types! { pub const CouncilBodyId: BodyId = BodyId::Executive; - // We are conservative with the XCM version we advertize. - pub const AdvertisedXcmVersion: u32 = 2; +} + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Parachain(1000).into()); } /// Type to convert a council origin to a Plurality `MultiLocation` value. @@ -172,7 +352,7 @@ pub type LocalOriginToLocation = ( // `Unit` body. CouncilToPlurality, // And a usual Signed origin to be used in XCM as a corresponding AccountId32 - SignedToAccountId32, + SignedToAccountId32, ); impl pallet_xcm::Config for Runtime { @@ -188,9 +368,17 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; // == Allow All type XcmReserveTransferFilter = Everything; // == Allow All type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; - type AdvertisedXcmVersion = AdvertisedXcmVersion; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = SovereignAccountOf; + type MaxLockers = ConstU32<8>; + type WeightInfo = crate::weights::pallet_xcm::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; } diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index 7329301a43bb..6f9a77566e22 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -1488,6 +1488,9 @@ pub type Migrations = ( pallet_scheduler::migration::v4::CleanupAgendas, parachains_disputes::migration::v1::MigrateToV1, parachains_configuration::migration::v4::MigrateToV4, + // "Use 2D weights in XCM v3" + pallet_xcm::migration::v1::MigrateToV1, + parachains_ump::migration::v1::MigrateToV1, ); /// Executive: handles dispatch to the various modules. @@ -1576,6 +1579,7 @@ mod benches { [pallet_utility, Utility] [pallet_vesting, Vesting] // XCM + [pallet_xcm, XcmPallet] [pallet_xcm_benchmarks::fungible, pallet_xcm_benchmarks::fungible::Pallet::] [pallet_xcm_benchmarks::generic, pallet_xcm_benchmarks::generic::Pallet::] ); @@ -1979,20 +1983,22 @@ sp_api::impl_runtime_apis! { use frame_system_benchmarking::Pallet as SystemBench; use frame_benchmarking::baseline::Pallet as Baseline; use xcm::latest::prelude::*; - use xcm_config::{CheckAccount, RocLocation, SovereignAccountOf, Statemine, XcmConfig}; + use xcm_config::{ + LocalCheckAccount, LocationConverter, Statemine, TokenLocation, XcmConfig, + }; impl frame_system_benchmarking::Config for Runtime {} impl frame_benchmarking::baseline::Config for Runtime {} impl pallet_xcm_benchmarks::Config for Runtime { type XcmConfig = XcmConfig; - type AccountIdConverter = SovereignAccountOf; + type AccountIdConverter = LocationConverter; fn valid_destination() -> Result { Ok(Statemine::get()) } - fn worst_case_holding() -> MultiAssets { + fn worst_case_holding(_depositable_count: u32) -> MultiAssets { // Rococo only knows about ROC vec![MultiAsset{ - id: Concrete(RocLocation::get()), + id: Concrete(TokenLocation::get()), fun: Fungible(1_000_000 * UNITS), }].into() } @@ -2001,24 +2007,23 @@ sp_api::impl_runtime_apis! { parameter_types! { pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( Statemine::get(), - MultiAsset { fun: Fungible(1 * UNITS), id: Concrete(RocLocation::get()) }, + MultiAsset { fun: Fungible(1 * UNITS), id: Concrete(TokenLocation::get()) }, )); pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = Some(( Statemine::get(), - MultiAsset { fun: Fungible(1 * UNITS), id: Concrete(RocLocation::get()) }, + MultiAsset { fun: Fungible(1 * UNITS), id: Concrete(TokenLocation::get()) }, )); } impl pallet_xcm_benchmarks::fungible::Config for Runtime { type TransactAsset = Balances; - type CheckedAccount = CheckAccount; + type CheckedAccount = LocalCheckAccount; type TrustedTeleporter = TrustedTeleporter; - type TrustedReserve = TrustedReserve; fn get_multi_asset() -> MultiAsset { MultiAsset { - id: Concrete(RocLocation::get()), + id: Concrete(TokenLocation::get()), fun: Fungible(1 * UNITS), } } @@ -2031,8 +2036,18 @@ sp_api::impl_runtime_apis! { (0u64, Response::Version(Default::default())) } - fn transact_origin() -> Result { - Ok(Statemine::get()) + fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + // Rococo doesn't support asset exchanges + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result { + // The XCM executor of Rococo doesn't have a configured `UniversalAliases` + Err(BenchmarkError::Skip) + } + + fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { + Ok((Statemine::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) } fn subscribe_origin() -> Result { @@ -2041,10 +2056,15 @@ sp_api::impl_runtime_apis! { fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { let origin = Statemine::get(); - let assets: MultiAssets = (Concrete(RocLocation::get()), 1_000 * UNITS).into(); + let assets: MultiAssets = (Concrete(TokenLocation::get()), 1_000 * UNITS).into(); let ticket = MultiLocation { parents: 0, interior: Here }; Ok((origin, ticket, assets)) } + + fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + // Rococo doesn't support asset locking + Err(BenchmarkError::Skip) + } } let whitelist: Vec = vec![ diff --git a/runtime/rococo/src/weights/mod.rs b/runtime/rococo/src/weights/mod.rs index f1657f95aa19..e6b345c37b84 100644 --- a/runtime/rococo/src/weights/mod.rs +++ b/runtime/rococo/src/weights/mod.rs @@ -39,6 +39,7 @@ pub mod pallet_tips; pub mod pallet_treasury; pub mod pallet_utility; pub mod pallet_vesting; +pub mod pallet_xcm; pub mod runtime_common_auctions; pub mod runtime_common_claims; pub mod runtime_common_crowdloan; diff --git a/runtime/rococo/src/weights/pallet_xcm.rs b/runtime/rococo/src/weights/pallet_xcm.rs new file mode 100644 index 000000000000..7534c87cb6de --- /dev/null +++ b/runtime/rococo/src/weights/pallet_xcm.rs @@ -0,0 +1,173 @@ +// Copyright 2017-2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . +//! Autogenerated weights for `pallet_xcm` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-12-16, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024 + +// Executed Command: +// /home/benchbot/cargo_target_dir/production/polkadot +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/polkadot/.git/.artifacts/bench.json +// --pallet=pallet_xcm +// --chain=rococo-dev +// --header=./file_header.txt +// --output=./runtime/rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weight functions for `pallet_xcm`. +pub struct WeightInfo(PhantomData); +impl pallet_xcm::WeightInfo for WeightInfo { + // Storage: Configuration ActiveConfig (r:1 w:0) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + fn send() -> Weight { + // Minimum execution time: 36_707 nanoseconds. + Weight::from_ref_time(37_718_000) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) + } + fn teleport_assets() -> Weight { + // Minimum execution time: 28_720 nanoseconds. + Weight::from_ref_time(29_098_000) + } + fn reserve_transfer_assets() -> Weight { + // Minimum execution time: 27_702 nanoseconds. + Weight::from_ref_time(28_517_000) + } + fn execute() -> Weight { + // Minimum execution time: 14_527 nanoseconds. + Weight::from_ref_time(14_823_000) + } + // Storage: XcmPallet SupportedVersion (r:0 w:1) + fn force_xcm_version() -> Weight { + // Minimum execution time: 16_306 nanoseconds. + Weight::from_ref_time(16_619_000) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: XcmPallet SafeXcmVersion (r:0 w:1) + fn force_default_xcm_version() -> Weight { + // Minimum execution time: 4_911 nanoseconds. + Weight::from_ref_time(5_080_000) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: XcmPallet VersionNotifiers (r:1 w:1) + // Storage: XcmPallet QueryCounter (r:1 w:1) + // Storage: Configuration ActiveConfig (r:1 w:0) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + // Storage: XcmPallet Queries (r:0 w:1) + fn force_subscribe_version_notify() -> Weight { + // Minimum execution time: 48_258 nanoseconds. + Weight::from_ref_time(49_130_000) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(6)) + } + // Storage: XcmPallet VersionNotifiers (r:1 w:1) + // Storage: Configuration ActiveConfig (r:1 w:0) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + // Storage: XcmPallet Queries (r:0 w:1) + fn force_unsubscribe_version_notify() -> Weight { + // Minimum execution time: 52_381 nanoseconds. + Weight::from_ref_time(53_183_000) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(5)) + } + // Storage: XcmPallet SupportedVersion (r:4 w:2) + fn migrate_supported_version() -> Weight { + // Minimum execution time: 16_915 nanoseconds. + Weight::from_ref_time(17_479_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: XcmPallet VersionNotifiers (r:4 w:2) + fn migrate_version_notifiers() -> Weight { + // Minimum execution time: 17_012 nanoseconds. + Weight::from_ref_time(17_319_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: XcmPallet VersionNotifyTargets (r:5 w:0) + fn already_notified_target() -> Weight { + // Minimum execution time: 19_489 nanoseconds. + Weight::from_ref_time(19_995_000) + .saturating_add(T::DbWeight::get().reads(5)) + } + // Storage: XcmPallet VersionNotifyTargets (r:2 w:1) + // Storage: Configuration ActiveConfig (r:1 w:0) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + fn notify_current_targets() -> Weight { + // Minimum execution time: 43_334 nanoseconds. + Weight::from_ref_time(43_983_000) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: XcmPallet VersionNotifyTargets (r:3 w:0) + fn notify_target_migration_fail() -> Weight { + // Minimum execution time: 8_627 nanoseconds. + Weight::from_ref_time(8_860_000) + .saturating_add(T::DbWeight::get().reads(3)) + } + // Storage: XcmPallet VersionNotifyTargets (r:4 w:2) + fn migrate_version_notify_targets() -> Weight { + // Minimum execution time: 17_679 nanoseconds. + Weight::from_ref_time(18_042_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: XcmPallet VersionNotifyTargets (r:4 w:2) + // Storage: Configuration ActiveConfig (r:1 w:0) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + fn migrate_and_notify_old_targets() -> Weight { + // Minimum execution time: 45_445 nanoseconds. + Weight::from_ref_time(48_369_000) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(5)) + } +} diff --git a/runtime/rococo/src/weights/runtime_parachains_ump.rs b/runtime/rococo/src/weights/runtime_parachains_ump.rs index a82fd5dbee60..734554e8aa3a 100644 --- a/runtime/rococo/src/weights/runtime_parachains_ump.rs +++ b/runtime/rococo/src/weights/runtime_parachains_ump.rs @@ -16,23 +16,25 @@ //! Autogenerated weights for `runtime_parachains::ump` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-16, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm6`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-01-16, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// /home/benchbot/cargo_target_dir/production/polkadot // benchmark // pallet -// --chain=rococo-dev // --steps=50 // --repeat=20 -// --pallet=runtime_parachains::ump // --extrinsic=* // --execution=wasm // --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/polkadot/.git/.artifacts/bench.json +// --pallet=runtime_parachains::ump +// --chain=rococo-dev // --header=./file_header.txt -// --output=./runtime/rococo/src/weights/runtime_parachains_ump.rs +// --output=./runtime/rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -46,26 +48,27 @@ pub struct WeightInfo(PhantomData); impl runtime_parachains::ump::WeightInfo for WeightInfo { /// The range of component `s` is `[0, 51200]`. fn process_upward_message(s: u32, ) -> Weight { - // Minimum execution time: 10_433 nanoseconds. - Weight::from_ref_time(6_809_084 as u64) - // Standard Error: 12 - .saturating_add(Weight::from_ref_time(1_973 as u64).saturating_mul(s as u64)) + // Minimum execution time: 10_224 nanoseconds. + Weight::from_ref_time(4_699_572) + // Standard Error: 11 + .saturating_add(Weight::from_ref_time(1_907).saturating_mul(s.into())) } // Storage: Ump NeedsDispatch (r:1 w:1) // Storage: Ump NextDispatchRoundStartWith (r:1 w:1) // Storage: Ump RelayDispatchQueues (r:0 w:1) // Storage: Ump RelayDispatchQueueSize (r:0 w:1) fn clean_ump_after_outgoing() -> Weight { - // Minimum execution time: 8_932 nanoseconds. - Weight::from_ref_time(9_171_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 9_180 nanoseconds. + Weight::from_ref_time(9_354_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Ump Overweight (r:1 w:1) + // Storage: Ump CounterForOverweight (r:1 w:1) fn service_overweight() -> Weight { - // Minimum execution time: 25_129 nanoseconds. - Weight::from_ref_time(25_441_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 28_704 nanoseconds. + Weight::from_ref_time(29_057_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } } diff --git a/runtime/rococo/src/weights/xcm/mod.rs b/runtime/rococo/src/weights/xcm/mod.rs index 2c5d4d6f05f9..49252e3662fb 100644 --- a/runtime/rococo/src/weights/xcm/mod.rs +++ b/runtime/rococo/src/weights/xcm/mod.rs @@ -4,10 +4,7 @@ mod pallet_xcm_benchmarks_generic; use crate::Runtime; use frame_support::weights::Weight; use sp_std::prelude::*; -use xcm::{ - latest::{prelude::*, Weight as XCMWeight}, - DoubleEncoded, -}; +use xcm::{latest::prelude::*, DoubleEncoded}; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmBalancesWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; @@ -31,15 +28,15 @@ impl From<&MultiAsset> for AssetTypes { } trait WeighMultiAssets { - fn weigh_multi_assets(&self, balances_weight: Weight) -> XCMWeight; + fn weigh_multi_assets(&self, balances_weight: Weight) -> Weight; } // Rococo only knows about one asset, the balances pallet. const MAX_ASSETS: u32 = 1; impl WeighMultiAssets for MultiAssetFilter { - fn weigh_multi_assets(&self, balances_weight: Weight) -> XCMWeight { - let weight = match self { + fn weigh_multi_assets(&self, balances_weight: Weight) -> Weight { + match self { Self::Definite(assets) => assets .inner() .into_iter() @@ -49,155 +46,213 @@ impl WeighMultiAssets for MultiAssetFilter { AssetTypes::Unknown => Weight::MAX, }) .fold(Weight::zero(), |acc, x| acc.saturating_add(x)), - Self::Wild(_) => balances_weight.saturating_mul(MAX_ASSETS as u64), - }; - - weight.ref_time() + Self::Wild(AllOf { .. } | AllOfCounted { .. }) => balances_weight, + Self::Wild(AllCounted(count)) => balances_weight.saturating_mul(*count as u64), + Self::Wild(All) => balances_weight.saturating_mul(MAX_ASSETS as u64), + } } } impl WeighMultiAssets for MultiAssets { - fn weigh_multi_assets(&self, balances_weight: Weight) -> XCMWeight { - let weight = self - .inner() + fn weigh_multi_assets(&self, balances_weight: Weight) -> Weight { + self.inner() .into_iter() .map(|m| >::from(m)) .map(|t| match t { AssetTypes::Balances => balances_weight, AssetTypes::Unknown => Weight::MAX, }) - .fold(Weight::zero(), |acc, x| acc.saturating_add(x)); - - weight.ref_time() + .fold(Weight::zero(), |acc, x| acc.saturating_add(x)) } } -pub struct RococoXcmWeight(core::marker::PhantomData); -impl XcmWeightInfo for RococoXcmWeight { - fn withdraw_asset(assets: &MultiAssets) -> XCMWeight { +pub struct RococoXcmWeight(core::marker::PhantomData); +impl XcmWeightInfo for RococoXcmWeight { + fn withdraw_asset(assets: &MultiAssets) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::withdraw_asset()) } - fn reserve_asset_deposited(assets: &MultiAssets) -> XCMWeight { + fn reserve_asset_deposited(assets: &MultiAssets) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::reserve_asset_deposited()) } - fn receive_teleported_asset(assets: &MultiAssets) -> XCMWeight { + fn receive_teleported_asset(assets: &MultiAssets) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::receive_teleported_asset()) } - fn query_response(_query_id: &u64, _response: &Response, _max_weight: &u64) -> XCMWeight { - XcmGeneric::::query_response().ref_time() + fn query_response( + _query_id: &u64, + _response: &Response, + _max_weight: &Weight, + _querier: &Option, + ) -> Weight { + XcmGeneric::::query_response() } - fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> XCMWeight { + fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::transfer_asset()) } fn transfer_reserve_asset( assets: &MultiAssets, _dest: &MultiLocation, _xcm: &Xcm<()>, - ) -> XCMWeight { + ) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::transfer_reserve_asset()) } fn transact( - _origin_type: &OriginKind, - _require_weight_at_most: &u64, - _call: &DoubleEncoded, - ) -> XCMWeight { - XcmGeneric::::transact().ref_time() + _origin_kind: &OriginKind, + _require_weight_at_most: &Weight, + _call: &DoubleEncoded, + ) -> Weight { + XcmGeneric::::transact() } fn hrmp_new_channel_open_request( _sender: &u32, _max_message_size: &u32, _max_capacity: &u32, - ) -> XCMWeight { + ) -> Weight { // XCM Executor does not currently support HRMP channel operations - Weight::MAX.ref_time() + Weight::MAX } - fn hrmp_channel_accepted(_recipient: &u32) -> XCMWeight { + fn hrmp_channel_accepted(_recipient: &u32) -> Weight { // XCM Executor does not currently support HRMP channel operations - Weight::MAX.ref_time() + Weight::MAX } - fn hrmp_channel_closing(_initiator: &u32, _sender: &u32, _recipient: &u32) -> XCMWeight { + fn hrmp_channel_closing(_initiator: &u32, _sender: &u32, _recipient: &u32) -> Weight { // XCM Executor does not currently support HRMP channel operations - Weight::MAX.ref_time() + Weight::MAX } - fn clear_origin() -> XCMWeight { - XcmGeneric::::clear_origin().ref_time() + fn clear_origin() -> Weight { + XcmGeneric::::clear_origin() } - fn descend_origin(_who: &InteriorMultiLocation) -> XCMWeight { - XcmGeneric::::descend_origin().ref_time() + fn descend_origin(_who: &InteriorMultiLocation) -> Weight { + XcmGeneric::::descend_origin() } - fn report_error( - _query_id: &QueryId, - _dest: &MultiLocation, - _max_response_weight: &u64, - ) -> XCMWeight { - XcmGeneric::::report_error().ref_time() + fn report_error(_query_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_error() } - fn deposit_asset( - assets: &MultiAssetFilter, - _max_assets: &u32, // TODO use max assets? - _dest: &MultiLocation, - ) -> XCMWeight { + fn deposit_asset(assets: &MultiAssetFilter, _dest: &MultiLocation) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::deposit_asset()) } fn deposit_reserve_asset( assets: &MultiAssetFilter, - _max_assets: &u32, // TODO use max assets? _dest: &MultiLocation, _xcm: &Xcm<()>, - ) -> XCMWeight { + ) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets) -> XCMWeight { - Weight::MAX.ref_time() // todo fix + fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets, _maximal: &bool) -> Weight { + // Rococo does not currently support exchange asset operations + Weight::MAX } fn initiate_reserve_withdraw( assets: &MultiAssetFilter, _reserve: &MultiLocation, _xcm: &Xcm<()>, - ) -> XCMWeight { + ) -> Weight { assets.weigh_multi_assets(XcmGeneric::::initiate_reserve_withdraw()) } fn initiate_teleport( assets: &MultiAssetFilter, _dest: &MultiLocation, _xcm: &Xcm<()>, - ) -> XCMWeight { + ) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::initiate_teleport()) } - fn query_holding( - _query_id: &u64, - _dest: &MultiLocation, - _assets: &MultiAssetFilter, - _max_response_weight: &u64, - ) -> XCMWeight { - XcmGeneric::::query_holding().ref_time() + fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + XcmGeneric::::report_holding() + } + fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { + XcmGeneric::::buy_execution() + } + fn refund_surplus() -> Weight { + XcmGeneric::::refund_surplus() + } + fn set_error_handler(_xcm: &Xcm) -> Weight { + XcmGeneric::::set_error_handler() + } + fn set_appendix(_xcm: &Xcm) -> Weight { + XcmGeneric::::set_appendix() + } + fn clear_error() -> Weight { + XcmGeneric::::clear_error() + } + fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> Weight { + XcmGeneric::::claim_asset() + } + fn trap(_code: &u64) -> Weight { + XcmGeneric::::trap() + } + fn subscribe_version(_query_id: &QueryId, _max_response_weight: &Weight) -> Weight { + XcmGeneric::::subscribe_version() + } + fn unsubscribe_version() -> Weight { + XcmGeneric::::unsubscribe_version() + } + fn burn_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + } + fn expect_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + } + fn expect_origin(_origin: &Option) -> Weight { + XcmGeneric::::expect_origin() + } + fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { + XcmGeneric::::expect_error() + } + fn query_pallet(_module_name: &Vec, _response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::query_pallet() + } + fn expect_pallet( + _index: &u32, + _name: &Vec, + _module_name: &Vec, + _crate_major: &u32, + _min_crate_minor: &u32, + ) -> Weight { + XcmGeneric::::expect_pallet() + } + fn report_transact_status(_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_transact_status() + } + fn clear_transact_status() -> Weight { + XcmGeneric::::clear_transact_status() + } + fn universal_origin(_: &Junction) -> Weight { + // Rococo does not currently support universal origin operations + Weight::MAX + } + fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { + Weight::MAX // todo fix } - fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> XCMWeight { - XcmGeneric::::buy_execution().ref_time() + fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Rococo does not currently support asset locking operations + Weight::MAX } - fn refund_surplus() -> XCMWeight { - XcmGeneric::::refund_surplus().ref_time() + fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Rococo does not currently support asset locking operations + Weight::MAX } - fn set_error_handler(_xcm: &Xcm) -> XCMWeight { - XcmGeneric::::set_error_handler().ref_time() + fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Rococo does not currently support asset locking operations + Weight::MAX } - fn set_appendix(_xcm: &Xcm) -> XCMWeight { - XcmGeneric::::set_appendix().ref_time() + fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Rococo does not currently support asset locking operations + Weight::MAX } - fn clear_error() -> XCMWeight { - XcmGeneric::::clear_error().ref_time() + fn set_fees_mode(_: &bool) -> Weight { + XcmGeneric::::set_fees_mode() } - fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> XCMWeight { - XcmGeneric::::claim_asset().ref_time() + fn set_topic(_topic: &[u8; 32]) -> Weight { + XcmGeneric::::set_topic() } - fn trap(_code: &u64) -> XCMWeight { - XcmGeneric::::trap().ref_time() + fn clear_topic() -> Weight { + XcmGeneric::::clear_topic() } - fn subscribe_version(_query_id: &QueryId, _max_response_weight: &u64) -> XCMWeight { - XcmGeneric::::subscribe_version().ref_time() + fn alias_origin(_: &MultiLocation) -> Weight { + // XCM Executor does not currently support alias origin operations + Weight::MAX } - fn unsubscribe_version() -> XCMWeight { - XcmGeneric::::unsubscribe_version().ref_time() + fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + XcmGeneric::::unpaid_execution() } } diff --git a/runtime/rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/runtime/rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 551ddc5903be..f2d786e85a46 100644 --- a/runtime/rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/runtime/rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -51,7 +51,7 @@ impl WeightInfo { // Storage: Configuration ActiveConfig (r:1 w:0) // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) // Storage: Dmp DownwardMessageQueues (r:1 w:1) - pub(crate) fn query_holding() -> Weight { + pub(crate) fn report_holding() -> Weight { Weight::from_ref_time(21_822_000 as u64) .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) @@ -133,4 +133,54 @@ impl WeightInfo { .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } + pub(crate) fn burn_asset() -> Weight { + Weight::from_ref_time(5_259_000 as u64) + } + pub(crate) fn expect_asset() -> Weight { + Weight::from_ref_time(3_745_000 as u64) + } + pub(crate) fn expect_origin() -> Weight { + Weight::from_ref_time(3_847_000 as u64) + } + pub(crate) fn expect_error() -> Weight { + Weight::from_ref_time(3_633_000 as u64) + } + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + pub(crate) fn query_pallet() -> Weight { + Weight::from_ref_time(21_645_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + pub(crate) fn expect_pallet() -> Weight { + Weight::from_ref_time(4_017_000 as u64) + } + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + pub(crate) fn report_transact_status() -> Weight { + Weight::from_ref_time(20_465_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + pub(crate) fn clear_transact_status() -> Weight { + Weight::from_ref_time(3_723_000 as u64) + } + pub(crate) fn set_topic() -> Weight { + Weight::from_ref_time(3_687_000 as u64) + } + pub(crate) fn clear_topic() -> Weight { + Weight::from_ref_time(3_654_000 as u64) + } + pub(crate) fn set_fees_mode() -> Weight { + Weight::from_ref_time(3_721_000 as u64) + } + pub(crate) fn unpaid_execution() -> Weight { + Weight::from_ref_time(3_111_000 as u64) + } } diff --git a/runtime/rococo/src/xcm_config.rs b/runtime/rococo/src/xcm_config.rs index 26a380dfcd20..4b6702afa7b2 100644 --- a/runtime/rococo/src/xcm_config.rs +++ b/runtime/rococo/src/xcm_config.rs @@ -17,44 +17,37 @@ //! XCM configuration for Rococo. use super::{ - parachains_origin, AccountId, Balances, CouncilCollective, ParaId, Runtime, RuntimeCall, - RuntimeEvent, RuntimeOrigin, WeightToFee, XcmPallet, + parachains_origin, AccountId, AllPalletsWithSystem, Balances, CouncilCollective, ParaId, + Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmPallet, +}; +use frame_support::{ + match_types, parameter_types, + traits::{Contains, Everything, Nothing}, + weights::Weight, }; -use frame_support::{match_types, parameter_types, traits::Everything}; use runtime_common::{xcm_sender, ToAuthor}; +use sp_core::ConstU32; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, - AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, BackingToPlurality, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, BackingToPlurality, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedWeightBounds, IsChildSystemParachain, IsConcrete, - LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, - TakeWeightCredit, UsingComponents, WeightInfoBounds, + MintLocation, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, + TakeWeightCredit, UsingComponents, WeightInfoBounds, WithComputedOrigin, }; +use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; parameter_types! { - /// The location of the ROC token, from the context of this chain. Since this token is native to this - /// chain, we make it synonymous with it and thus it is the `Here` location, which means "equivalent to - /// the context". - pub const RocLocation: MultiLocation = Here.into(); - /// The Rococo network ID. This is named. - pub RococoNetwork: NetworkId = - NetworkId::Named(b"Rococo".to_vec().try_into().expect("shorter than length limit; qed")); - /// Our XCM location ancestry - i.e. what, if anything, `Parent` means evaluated in our context. Since - /// Rococo is a top-level relay-chain, there is no ancestry. - pub const Ancestry: MultiLocation = Here.into(); - /// The check account, which holds any native assets that have been teleported out and not back in (yet). + pub const TokenLocation: MultiLocation = Here.into_location(); + pub const ThisNetwork: NetworkId = NetworkId::Rococo; + pub UniversalLocation: InteriorMultiLocation = ThisNetwork::get().into(); pub CheckAccount: AccountId = XcmPallet::check_account(); + pub LocalCheckAccount: (AccountId, MintLocation) = (CheckAccount::get(), MintLocation::Local); } -/// The canonical means of converting a `MultiLocation` into an `AccountId`, used when we want to determine -/// the sovereign account controlled by a location. -pub type SovereignAccountOf = ( - // We can convert a child parachain using the standard `AccountId` conversion. - ChildParachainConvertsVia, - // We can directly alias an `AccountId32` into a local account. - AccountId32Aliases, -); +pub type LocationConverter = + (ChildParachainConvertsVia, AccountId32Aliases); /// Our asset transactor. This is what allows us to interest with the runtime facilities from the point of /// view of XCM-only concepts like `MultiLocation` and `MultiAsset`. @@ -64,63 +57,62 @@ pub type LocalAssetTransactor = XcmCurrencyAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, + IsConcrete, // We can convert the MultiLocations with our converter above: - SovereignAccountOf, + LocationConverter, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, // We track our teleports in/out to keep total issuance correct. - CheckAccount, + LocalCheckAccount, >; /// The means that we convert an the XCM message origin location into a local dispatch origin. type LocalOriginConverter = ( // A `Signed` origin of the sovereign account that the original location controls. - SovereignSignedViaLocation, + SovereignSignedViaLocation, // A child parachain, natively expressed, has the `Parachain` origin. ChildParachainAsNative, // The AccountId32 location type can be expressed natively as a `Signed` origin. - SignedAccountId32AsNative, + SignedAccountId32AsNative, // A system child parachain, expressed as a Superuser, converts to the `Root` origin. ChildSystemParachainAsSuperuser, ); parameter_types! { /// The amount of weight an XCM operation takes. This is a safe overestimate. - pub const BaseXcmWeight: u64 = 1_000_000_000; - /// Maximum number of instructions in a single XCM fragment. A sanity check against weight - /// calculations getting too crazy. - pub const MaxInstructions: u32 = 100; + pub const BaseXcmWeight: Weight = Weight::from_parts(1_000_000_000, 64 * 1024); } /// The XCM router. When we want to send an XCM message, we use this type. It amalgamates all of our /// individual routers. pub type XcmRouter = ( // Only one router so far - use DMP to communicate with child parachains. - xcm_sender::ChildParachainRouter, + xcm_sender::ChildParachainRouter, ); parameter_types! { - pub const Rococo: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete(RocLocation::get()) }); - pub const Statemine: MultiLocation = Parachain(1000).into(); - pub const Contracts: MultiLocation = Parachain(1002).into(); - pub const Encointer: MultiLocation = Parachain(1003).into(); - pub const Tick: MultiLocation = Parachain(100).into(); - pub const Trick: MultiLocation = Parachain(110).into(); - pub const Track: MultiLocation = Parachain(120).into(); - pub const RococoForTick: (MultiAssetFilter, MultiLocation) = (Rococo::get(), Tick::get()); - pub const RococoForTrick: (MultiAssetFilter, MultiLocation) = (Rococo::get(), Trick::get()); - pub const RococoForTrack: (MultiAssetFilter, MultiLocation) = (Rococo::get(), Track::get()); - pub const RococoForStatemine: (MultiAssetFilter, MultiLocation) = (Rococo::get(), Statemine::get()); - pub const RococoForContracts: (MultiAssetFilter, MultiLocation) = (Rococo::get(), Contracts::get()); - pub const RococoForEncointer: (MultiAssetFilter, MultiLocation) = (Rococo::get(), Encointer::get()); + pub const Roc: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete(TokenLocation::get()) }); + pub const Statemine: MultiLocation = Parachain(1000).into_location(); + pub const Contracts: MultiLocation = Parachain(1002).into_location(); + pub const Encointer: MultiLocation = Parachain(1003).into_location(); + pub const Tick: MultiLocation = Parachain(100).into_location(); + pub const Trick: MultiLocation = Parachain(110).into_location(); + pub const Track: MultiLocation = Parachain(120).into_location(); + pub const RocForTick: (MultiAssetFilter, MultiLocation) = (Roc::get(), Tick::get()); + pub const RocForTrick: (MultiAssetFilter, MultiLocation) = (Roc::get(), Trick::get()); + pub const RocForTrack: (MultiAssetFilter, MultiLocation) = (Roc::get(), Track::get()); + pub const RocForStatemine: (MultiAssetFilter, MultiLocation) = (Roc::get(), Statemine::get()); + pub const RocForContracts: (MultiAssetFilter, MultiLocation) = (Roc::get(), Contracts::get()); + pub const RocForEncointer: (MultiAssetFilter, MultiLocation) = (Roc::get(), Encointer::get()); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } pub type TrustedTeleporters = ( - xcm_builder::Case, - xcm_builder::Case, - xcm_builder::Case, - xcm_builder::Case, - xcm_builder::Case, - xcm_builder::Case, + xcm_builder::Case, + xcm_builder::Case, + xcm_builder::Case, + xcm_builder::Case, + xcm_builder::Case, + xcm_builder::Case, ); match_types! { @@ -133,16 +125,150 @@ match_types! { pub type Barrier = ( // Weight that is paid for may be consumed. TakeWeightCredit, - // If the message is one that immediately attemps to pay for execution, then allow it. - AllowTopLevelPaidExecutionFrom, - // Messages coming from system parachains need not pay for execution. - AllowUnpaidExecutionFrom>, // Expected responses are OK. AllowKnownQueryResponses, - // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, + WithComputedOrigin< + ( + // If the message is one that immediately attemps to pay for execution, then allow it. + AllowTopLevelPaidExecutionFrom, + // Messages coming from system parachains need not pay for execution. + AllowExplicitUnpaidExecutionFrom>, + // Subscriptions for version tracking are OK. + AllowSubscriptionsFrom, + ), + UniversalLocation, + ConstU32<8>, + >, ); +/// A call filter for the XCM Transact instruction. This is a temporary measure until we +/// properly account for proof size weights. +/// +/// Calls that are allowed through this filter must: +/// 1. Have a fixed weight; +/// 2. Cannot lead to another call being made; +/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. +pub struct SafeCallFilter; +impl Contains for SafeCallFilter { + fn contains(call: &RuntimeCall) -> bool { + #[cfg(feature = "runtime-benchmarks")] + { + if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { + return true + } + } + + match call { + RuntimeCall::System( + frame_system::Call::kill_prefix { .. } | frame_system::Call::set_heap_pages { .. }, + ) | + RuntimeCall::Babe(..) | + RuntimeCall::Timestamp(..) | + RuntimeCall::Indices(..) | + RuntimeCall::Balances(..) | + RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | + RuntimeCall::Grandpa(..) | + RuntimeCall::ImOnline(..) | + RuntimeCall::Democracy( + pallet_democracy::Call::second { .. } | + pallet_democracy::Call::vote { .. } | + pallet_democracy::Call::emergency_cancel { .. } | + pallet_democracy::Call::fast_track { .. } | + pallet_democracy::Call::veto_external { .. } | + pallet_democracy::Call::cancel_referendum { .. } | + pallet_democracy::Call::delegate { .. } | + pallet_democracy::Call::undelegate { .. } | + pallet_democracy::Call::clear_public_proposals { .. } | + pallet_democracy::Call::unlock { .. } | + pallet_democracy::Call::remove_vote { .. } | + pallet_democracy::Call::remove_other_vote { .. } | + pallet_democracy::Call::blacklist { .. } | + pallet_democracy::Call::cancel_proposal { .. }, + ) | + RuntimeCall::Council( + pallet_collective::Call::vote { .. } | + pallet_collective::Call::close_old_weight { .. } | + pallet_collective::Call::disapprove_proposal { .. } | + pallet_collective::Call::close { .. }, + ) | + RuntimeCall::TechnicalCommittee( + pallet_collective::Call::vote { .. } | + pallet_collective::Call::close_old_weight { .. } | + pallet_collective::Call::disapprove_proposal { .. } | + pallet_collective::Call::close { .. }, + ) | + RuntimeCall::PhragmenElection( + pallet_elections_phragmen::Call::remove_voter { .. } | + pallet_elections_phragmen::Call::submit_candidacy { .. } | + pallet_elections_phragmen::Call::renounce_candidacy { .. } | + pallet_elections_phragmen::Call::remove_member { .. } | + pallet_elections_phragmen::Call::clean_defunct_voters { .. }, + ) | + RuntimeCall::TechnicalMembership( + pallet_membership::Call::add_member { .. } | + pallet_membership::Call::remove_member { .. } | + pallet_membership::Call::swap_member { .. } | + pallet_membership::Call::change_key { .. } | + pallet_membership::Call::set_prime { .. } | + pallet_membership::Call::clear_prime { .. }, + ) | + RuntimeCall::Treasury(..) | + RuntimeCall::Claims( + super::claims::Call::claim { .. } | + super::claims::Call::mint_claim { .. } | + super::claims::Call::move_claim { .. }, + ) | + RuntimeCall::Utility(pallet_utility::Call::as_derivative { .. }) | + RuntimeCall::Identity( + pallet_identity::Call::add_registrar { .. } | + pallet_identity::Call::set_identity { .. } | + pallet_identity::Call::clear_identity { .. } | + pallet_identity::Call::request_judgement { .. } | + pallet_identity::Call::cancel_request { .. } | + pallet_identity::Call::set_fee { .. } | + pallet_identity::Call::set_account_id { .. } | + pallet_identity::Call::set_fields { .. } | + pallet_identity::Call::provide_judgement { .. } | + pallet_identity::Call::kill_identity { .. } | + pallet_identity::Call::add_sub { .. } | + pallet_identity::Call::rename_sub { .. } | + pallet_identity::Call::remove_sub { .. } | + pallet_identity::Call::quit_sub { .. }, + ) | + RuntimeCall::Society( + pallet_society::Call::bid { .. } | + pallet_society::Call::unbid { .. } | + pallet_society::Call::vouch { .. } | + pallet_society::Call::unvouch { .. } | + pallet_society::Call::vote { .. } | + pallet_society::Call::defender_vote { .. } | + pallet_society::Call::payout { .. } | + pallet_society::Call::unfound { .. } | + pallet_society::Call::judge_suspended_member { .. } | + pallet_society::Call::judge_suspended_candidate { .. } | + pallet_society::Call::set_max_members { .. }, + ) | + RuntimeCall::Recovery(..) | + RuntimeCall::Vesting(..) | + RuntimeCall::Bounties( + pallet_bounties::Call::propose_bounty { .. } | + pallet_bounties::Call::approve_bounty { .. } | + pallet_bounties::Call::propose_curator { .. } | + pallet_bounties::Call::unassign_curator { .. } | + pallet_bounties::Call::accept_curator { .. } | + pallet_bounties::Call::award_bounty { .. } | + pallet_bounties::Call::claim_bounty { .. } | + pallet_bounties::Call::close_bounty { .. }, + ) | + RuntimeCall::ChildBounties(..) | + RuntimeCall::XcmPallet(pallet_xcm::Call::limited_reserve_transfer_assets { + .. + }) => true, + _ => false, + } + } +} + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -151,19 +277,28 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = LocalOriginConverter; type IsReserve = (); type IsTeleporter = TrustedTeleporters; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = WeightInfoBounds< crate::weights::xcm::RococoXcmWeight, RuntimeCall, MaxInstructions, >; - // The weight trader piggybacks on the existing transaction-fee conversion logic. - type Trader = UsingComponents>; + type Trader = + UsingComponents>; type ResponseHandler = XcmPallet; type AssetTrap = XcmPallet; + type AssetLocker = (); + type AssetExchanger = (); type AssetClaims = XcmPallet; type SubscriptionService = XcmPallet; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = WithOriginFilter; + type SafeCallFilter = SafeCallFilter; } parameter_types! { @@ -174,6 +309,11 @@ parameter_types! { pub const CouncilBodyId: BodyId = BodyId::Executive; } +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Parachain(1000).into()); +} + /// Type to convert the council origin to a Plurality `MultiLocation` value. pub type CouncilToPlurality = BackingToPlurality< RuntimeOrigin, @@ -188,7 +328,7 @@ pub type LocalOriginToLocation = ( // `Unit` body. CouncilToPlurality, // And a usual Signed origin to be used in XCM as a corresponding AccountId32 - SignedToAccountId32, + SignedToAccountId32, ); impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -197,16 +337,23 @@ impl pallet_xcm::Config for Runtime { // Anyone can execute XCM messages locally. type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; type XcmExecuteFilter = Everything; - type XcmExecutor = xcm_executor::XcmExecutor; - // Anyone is able to use teleportation regardless of who they are and what they want to teleport. + type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; // Anyone is able to use reserve transfers regardless of who they are and what they want to // transfer. type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = IsConcrete; + type TrustedLockers = (); + type SovereignAccountOf = LocationConverter; + type MaxLockers = ConstU32<8>; + type WeightInfo = crate::weights::pallet_xcm::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; } diff --git a/runtime/test-runtime/Cargo.toml b/runtime/test-runtime/Cargo.toml index 0bb3b1954f9b..e32107a69b48 100644 --- a/runtime/test-runtime/Cargo.toml +++ b/runtime/test-runtime/Cargo.toml @@ -129,3 +129,7 @@ std = [ "frame-election-provider-support/std", "pallet-sudo/std", ] + +runtime-benchmarks = [ + "pallet-xcm/runtime-benchmarks", +] diff --git a/runtime/test-runtime/src/lib.rs b/runtime/test-runtime/src/lib.rs index 8d95ce9b7674..5e418e35f2c2 100644 --- a/runtime/test-runtime/src/lib.rs +++ b/runtime/test-runtime/src/lib.rs @@ -131,7 +131,7 @@ parameter_types! { } impl frame_system::Config for Runtime { - type BaseCallFilter = frame_support::traits::Everything; + type BaseCallFilter = Everything; type BlockWeights = BlockWeights; type BlockLength = BlockLength; type DbWeight = (); @@ -537,34 +537,6 @@ impl parachains_ump::Config for Runtime { type WeightInfo = parachains_ump::TestWeightInfo; } -parameter_types! { - pub const BaseXcmWeight: xcm::latest::Weight = 1_000; - pub const AnyNetwork: xcm::latest::NetworkId = xcm::latest::NetworkId::Any; - pub const MaxInstructions: u32 = 100; -} - -pub type LocalOriginToLocation = - xcm_builder::SignedToAccountId32; - -impl pallet_xcm::Config for Runtime { - // The config types here are entirely configurable, since the only one that is sorely needed - // is `XcmExecutor`, which will be used in unit tests located in xcm-executor. - type RuntimeEvent = RuntimeEvent; - type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; - type LocationInverter = xcm_config::InvertNothing; - type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; - type Weigher = xcm_builder::FixedWeightBounds; - type XcmRouter = xcm_config::DoNothingRouter; - type XcmExecuteFilter = Everything; - type XcmExecutor = xcm_executor::XcmExecutor; - type XcmTeleportFilter = Everything; - type XcmReserveTransferFilter = Everything; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; - type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; -} - impl parachains_hrmp::Config for Runtime { type RuntimeOrigin = RuntimeOrigin; type RuntimeEvent = RuntimeEvent; @@ -628,8 +600,9 @@ pub mod pallet_test_notifier { .using_encoded(|mut d| <[u8; 32]>::decode(&mut d)) .map_err(|_| Error::::BadAccountFormat)?; let qid = pallet_xcm::Pallet::::new_query( - Junction::AccountId32 { network: Any, id }.into(), + Junction::AccountId32 { network: None, id }, 100u32.into(), + Here, ); Self::deposit_event(Event::::QueryPrepared(qid)); Ok(()) @@ -645,9 +618,10 @@ pub mod pallet_test_notifier { let call = Call::::notification_received { query_id: 0, response: Default::default() }; let qid = pallet_xcm::Pallet::::new_notify_query( - Junction::AccountId32 { network: Any, id }.into(), + Junction::AccountId32 { network: None, id }, ::RuntimeCall::from(call), 100u32.into(), + Here, ); Self::deposit_event(Event::::NotifyQueryPrepared(qid)); Ok(()) diff --git a/runtime/test-runtime/src/xcm_config.rs b/runtime/test-runtime/src/xcm_config.rs index 217995f31fe4..bfe9c6ad1ad8 100644 --- a/runtime/test-runtime/src/xcm_config.rs +++ b/runtime/test-runtime/src/xcm_config.rs @@ -14,30 +14,44 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use frame_support::{parameter_types, traits::Everything}; -use xcm::latest::{prelude::*, Weight as XCMWeight}; -use xcm_builder::{AllowUnpaidExecutionFrom, FixedWeightBounds, SignedToAccountId32}; +use frame_support::{ + parameter_types, + traits::{Everything, Nothing}, + weights::Weight, +}; +use xcm::latest::prelude::*; +use xcm_builder::{ + AllowUnpaidExecutionFrom, EnsureXcmOrigin, FixedWeightBounds, SignedAccountId32AsNative, + SignedToAccountId32, +}; use xcm_executor::{ - traits::{InvertLocation, TransactAsset, WeightTrader}, + traits::{TransactAsset, WeightTrader}, Assets, }; parameter_types! { - pub const OurNetwork: NetworkId = NetworkId::Polkadot; + pub const BaseXcmWeight: xcm::latest::Weight = Weight::from_parts(1_000, 1_000); + pub const AnyNetwork: Option = None; pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 16; + pub const UniversalLocation: xcm::latest::InteriorMultiLocation = xcm::latest::Junctions::Here; } /// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior location /// of this chain. pub type LocalOriginToLocation = ( // And a usual Signed origin to be used in XCM as a corresponding AccountId32 - SignedToAccountId32, + SignedToAccountId32, ); pub struct DoNothingRouter; impl SendXcm for DoNothingRouter { - fn send_xcm(_dest: impl Into, _msg: Xcm<()>) -> SendResult { - Ok(()) + type Ticket = (); + fn validate(_dest: &mut Option, _msg: &mut Option>) -> SendResult<()> { + Ok(((), MultiAssets::new())) + } + fn deliver(_: ()) -> Result { + Ok([0; 32]) } } @@ -45,11 +59,15 @@ pub type Barrier = AllowUnpaidExecutionFrom; pub struct DummyAssetTransactor; impl TransactAsset for DummyAssetTransactor { - fn deposit_asset(_what: &MultiAsset, _who: &MultiLocation) -> XcmResult { + fn deposit_asset(_what: &MultiAsset, _who: &MultiLocation, _context: &XcmContext) -> XcmResult { Ok(()) } - fn withdraw_asset(_what: &MultiAsset, _who: &MultiLocation) -> Result { + fn withdraw_asset( + _what: &MultiAsset, + _who: &MultiLocation, + _maybe_context: Option<&XcmContext>, + ) -> Result { let asset: MultiAsset = (Parent, 100_000).into(); Ok(asset.into()) } @@ -61,35 +79,71 @@ impl WeightTrader for DummyWeightTrader { DummyWeightTrader } - fn buy_weight(&mut self, _weight: XCMWeight, _payment: Assets) -> Result { + fn buy_weight(&mut self, _weight: Weight, _payment: Assets) -> Result { Ok(Assets::default()) } } -pub struct InvertNothing; -impl InvertLocation for InvertNothing { - fn invert_location(_: &MultiLocation) -> sp_std::result::Result { - Ok(Here.into()) - } - fn ancestry() -> MultiLocation { - Here.into() - } -} +type OriginConverter = ( + pallet_xcm::XcmPassthrough, + SignedAccountId32AsNative, +); pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = super::RuntimeCall; type XcmSender = DoNothingRouter; type AssetTransactor = DummyAssetTransactor; - type OriginConverter = pallet_xcm::XcmPassthrough; + type OriginConverter = OriginConverter; type IsReserve = (); type IsTeleporter = (); - type LocationInverter = InvertNothing; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type Trader = DummyWeightTrader; type ResponseHandler = super::Xcm; type AssetTrap = super::Xcm; + type AssetLocker = (); + type AssetExchanger = (); type AssetClaims = super::Xcm; type SubscriptionService = super::Xcm; + type PalletInstancesInfo = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = super::RuntimeCall; + type SafeCallFilter = Everything; +} + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(xcm::latest::Junctions::Here.into()); +} + +impl pallet_xcm::Config for crate::Runtime { + // The config types here are entirely configurable, since the only one that is sorely needed + // is `XcmExecutor`, which will be used in unit tests located in xcm-executor. + type RuntimeEvent = crate::RuntimeEvent; + type ExecuteXcmOrigin = EnsureXcmOrigin; + type UniversalLocation = UniversalLocation; + type SendXcmOrigin = EnsureXcmOrigin; + type Weigher = FixedWeightBounds; + type XcmRouter = DoNothingRouter; + type XcmExecuteFilter = Everything; + type XcmExecutor = xcm_executor::XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Everything; + type RuntimeOrigin = crate::RuntimeOrigin; + type RuntimeCall = crate::RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = crate::Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = (); + type MaxLockers = frame_support::traits::ConstU32<8>; + type WeightInfo = pallet_xcm::TestWeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; } diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index ac1b82637941..aa5e2c0d029e 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -1247,6 +1247,9 @@ pub type Migrations = ( parachains_disputes::migration::v1::MigrateToV1, parachains_configuration::migration::v4::MigrateToV4, init_state_migration::InitMigrate, + // "Use 2D weights in XCM v3" + pallet_xcm::migration::v1::MigrateToV1, + parachains_ump::migration::v1::MigrateToV1, ); /// Unchecked extrinsic type as expected by this runtime. @@ -1309,6 +1312,7 @@ mod benches { [pallet_utility, Utility] [pallet_vesting, Vesting] // XCM + [pallet_xcm, XcmPallet] // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] @@ -1728,10 +1732,10 @@ sp_api::impl_runtime_apis! { impl runtime_parachains::disputes::slashing::benchmarking::Config for Runtime {} use xcm::latest::{ - AssetId::*, Fungibility::*, Junctions::*, MultiAsset, MultiAssets, MultiLocation, - Response, + AssetId::*, Fungibility::*, Junction, Junctions::*, MultiAsset, MultiAssets, + MultiLocation, Response, }; - use xcm_config::{Westmint, WndLocation}; + use xcm_config::{Westmint, TokenLocation}; impl pallet_xcm_benchmarks::Config for Runtime { type XcmConfig = xcm_config::XcmConfig; @@ -1739,10 +1743,10 @@ sp_api::impl_runtime_apis! { fn valid_destination() -> Result { Ok(Westmint::get()) } - fn worst_case_holding() -> MultiAssets { + fn worst_case_holding(_depositable_count: u32) -> MultiAssets { // Westend only knows about WND. vec![MultiAsset{ - id: Concrete(WndLocation::get()), + id: Concrete(TokenLocation::get()), fun: Fungible(1_000_000 * UNITS), }].into() } @@ -1751,24 +1755,19 @@ sp_api::impl_runtime_apis! { parameter_types! { pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( Westmint::get(), - MultiAsset { fun: Fungible(1 * UNITS), id: Concrete(WndLocation::get()) }, - )); - pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = Some(( - Westmint::get(), - MultiAsset { fun: Fungible(1 * UNITS), id: Concrete(WndLocation::get()) }, + MultiAsset { fun: Fungible(1 * UNITS), id: Concrete(TokenLocation::get()) }, )); } impl pallet_xcm_benchmarks::fungible::Config for Runtime { type TransactAsset = Balances; - type CheckedAccount = xcm_config::CheckAccount; + type CheckedAccount = xcm_config::LocalCheckAccount; type TrustedTeleporter = TrustedTeleporter; - type TrustedReserve = TrustedReserve; fn get_multi_asset() -> MultiAsset { MultiAsset { - id: Concrete(WndLocation::get()), + id: Concrete(TokenLocation::get()), fun: Fungible(1 * UNITS), } } @@ -1781,8 +1780,18 @@ sp_api::impl_runtime_apis! { (0u64, Response::Version(Default::default())) } - fn transact_origin() -> Result { - Ok(Westmint::get()) + fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + // Westend doesn't support asset exchanges + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result { + // The XCM executor of Westend doesn't have a configured `UniversalAliases` + Err(BenchmarkError::Skip) + } + + fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { + Ok((Westmint::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) } fn subscribe_origin() -> Result { @@ -1791,10 +1800,15 @@ sp_api::impl_runtime_apis! { fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { let origin = Westmint::get(); - let assets: MultiAssets = (Concrete(WndLocation::get()), 1_000 * UNITS).into(); + let assets: MultiAssets = (Concrete(TokenLocation::get()), 1_000 * UNITS).into(); let ticket = MultiLocation { parents: 0, interior: Here }; Ok((origin, ticket, assets)) } + + fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + // Westend doesn't support asset locking + Err(BenchmarkError::Skip) + } } type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; diff --git a/runtime/westend/src/tests.rs b/runtime/westend/src/tests.rs index 385bf55caaa6..401b575255a8 100644 --- a/runtime/westend/src/tests.rs +++ b/runtime/westend/src/tests.rs @@ -17,7 +17,7 @@ //! Tests for the Westend Runtime Configuration use crate::*; -use xcm::latest::{AssetId::*, Fungibility::*, MultiLocation}; +use xcm::latest::prelude::*; #[test] fn remove_keys_weight_is_sensible() { @@ -57,9 +57,9 @@ fn sanity_check_teleport_assets_weight() { // so this test will certainly ensure that this problem does not occur. use frame_support::dispatch::GetDispatchInfo; let weight = pallet_xcm::Call::::teleport_assets { - dest: Box::new(xcm::VersionedMultiLocation::V1(MultiLocation::here())), - beneficiary: Box::new(xcm::VersionedMultiLocation::V1(MultiLocation::here())), - assets: Box::new((Concrete(MultiLocation::here()), Fungible(200_000)).into()), + dest: Box::new(Here.into()), + beneficiary: Box::new(Here.into()), + assets: Box::new((Here, 200_000).into()), fee_asset_item: 0, } .get_dispatch_info() diff --git a/runtime/westend/src/weights/mod.rs b/runtime/westend/src/weights/mod.rs index 216b3dcdf4ec..5ca093c618d5 100644 --- a/runtime/westend/src/weights/mod.rs +++ b/runtime/westend/src/weights/mod.rs @@ -34,6 +34,7 @@ pub mod pallet_staking; pub mod pallet_timestamp; pub mod pallet_utility; pub mod pallet_vesting; +pub mod pallet_xcm; pub mod runtime_common_auctions; pub mod runtime_common_crowdloan; pub mod runtime_common_paras_registrar; diff --git a/runtime/westend/src/weights/pallet_xcm.rs b/runtime/westend/src/weights/pallet_xcm.rs new file mode 100644 index 000000000000..50478ed21f1b --- /dev/null +++ b/runtime/westend/src/weights/pallet_xcm.rs @@ -0,0 +1,169 @@ +// Copyright 2017-2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . +//! Autogenerated weights for `pallet_xcm` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-12-16, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 + +// Executed Command: +// /home/benchbot/cargo_target_dir/production/polkadot +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/polkadot/.git/.artifacts/bench.json +// --pallet=pallet_xcm +// --chain=westend-dev +// --header=./file_header.txt +// --output=./runtime/westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weight functions for `pallet_xcm`. +pub struct WeightInfo(PhantomData); +impl pallet_xcm::WeightInfo for WeightInfo { + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + fn send() -> Weight { + // Minimum execution time: 36_453 nanoseconds. + Weight::from_ref_time(37_511_000) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(3)) + } + fn teleport_assets() -> Weight { + // Minimum execution time: 28_144 nanoseconds. + Weight::from_ref_time(28_952_000) + } + fn reserve_transfer_assets() -> Weight { + // Minimum execution time: 28_245 nanoseconds. + Weight::from_ref_time(28_710_000) + } + // Storage: Benchmark Override (r:0 w:0) + fn execute() -> Weight { + // Minimum execution time: 18_446_744_073_709_551 nanoseconds. + Weight::from_ref_time(18_446_744_073_709_551_000) + } + // Storage: XcmPallet SupportedVersion (r:0 w:1) + fn force_xcm_version() -> Weight { + // Minimum execution time: 15_350 nanoseconds. + Weight::from_ref_time(15_829_000) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: XcmPallet SafeXcmVersion (r:0 w:1) + fn force_default_xcm_version() -> Weight { + // Minimum execution time: 4_482 nanoseconds. + Weight::from_ref_time(4_588_000) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: XcmPallet VersionNotifiers (r:1 w:1) + // Storage: XcmPallet QueryCounter (r:1 w:1) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + // Storage: XcmPallet Queries (r:0 w:1) + fn force_subscribe_version_notify() -> Weight { + // Minimum execution time: 41_818 nanoseconds. + Weight::from_ref_time(42_824_000) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(6)) + } + // Storage: XcmPallet VersionNotifiers (r:1 w:1) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + // Storage: XcmPallet Queries (r:0 w:1) + fn force_unsubscribe_version_notify() -> Weight { + // Minimum execution time: 45_488 nanoseconds. + Weight::from_ref_time(46_295_000) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(5)) + } + // Storage: XcmPallet SupportedVersion (r:4 w:2) + fn migrate_supported_version() -> Weight { + // Minimum execution time: 14_614 nanoseconds. + Weight::from_ref_time(14_829_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: XcmPallet VersionNotifiers (r:4 w:2) + fn migrate_version_notifiers() -> Weight { + // Minimum execution time: 14_724 nanoseconds. + Weight::from_ref_time(14_915_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: XcmPallet VersionNotifyTargets (r:5 w:0) + fn already_notified_target() -> Weight { + // Minimum execution time: 16_814 nanoseconds. + Weight::from_ref_time(17_007_000) + .saturating_add(T::DbWeight::get().reads(5)) + } + // Storage: XcmPallet VersionNotifyTargets (r:2 w:1) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + fn notify_current_targets() -> Weight { + // Minimum execution time: 37_273 nanoseconds. + Weight::from_ref_time(37_869_000) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: XcmPallet VersionNotifyTargets (r:3 w:0) + fn notify_target_migration_fail() -> Weight { + // Minimum execution time: 7_517 nanoseconds. + Weight::from_ref_time(7_682_000) + .saturating_add(T::DbWeight::get().reads(3)) + } + // Storage: XcmPallet VersionNotifyTargets (r:4 w:2) + fn migrate_version_notify_targets() -> Weight { + // Minimum execution time: 15_088 nanoseconds. + Weight::from_ref_time(15_464_000) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: XcmPallet VersionNotifyTargets (r:4 w:2) + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + fn migrate_and_notify_old_targets() -> Weight { + // Minimum execution time: 42_829 nanoseconds. + Weight::from_ref_time(43_814_000) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(5)) + } +} diff --git a/runtime/westend/src/weights/runtime_parachains_ump.rs b/runtime/westend/src/weights/runtime_parachains_ump.rs index 6afe1a8685dc..6f42ca36e287 100644 --- a/runtime/westend/src/weights/runtime_parachains_ump.rs +++ b/runtime/westend/src/weights/runtime_parachains_ump.rs @@ -16,23 +16,25 @@ //! Autogenerated weights for `runtime_parachains::ump` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-16, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm6`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-01-16, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// /home/benchbot/cargo_target_dir/production/polkadot // benchmark // pallet -// --chain=westend-dev // --steps=50 // --repeat=20 -// --pallet=runtime_parachains::ump // --extrinsic=* // --execution=wasm // --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/polkadot/.git/.artifacts/bench.json +// --pallet=runtime_parachains::ump +// --chain=westend-dev // --header=./file_header.txt -// --output=./runtime/westend/src/weights/runtime_parachains_ump.rs +// --output=./runtime/westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -46,26 +48,27 @@ pub struct WeightInfo(PhantomData); impl runtime_parachains::ump::WeightInfo for WeightInfo { /// The range of component `s` is `[0, 51200]`. fn process_upward_message(s: u32, ) -> Weight { - // Minimum execution time: 10_921 nanoseconds. - Weight::from_ref_time(5_417_303 as u64) - // Standard Error: 13 - .saturating_add(Weight::from_ref_time(1_939 as u64).saturating_mul(s as u64)) + // Minimum execution time: 10_155 nanoseconds. + Weight::from_ref_time(4_325_532) + // Standard Error: 11 + .saturating_add(Weight::from_ref_time(1_926).saturating_mul(s.into())) } // Storage: Ump NeedsDispatch (r:1 w:1) // Storage: Ump NextDispatchRoundStartWith (r:1 w:1) // Storage: Ump RelayDispatchQueues (r:0 w:1) // Storage: Ump RelayDispatchQueueSize (r:0 w:1) fn clean_ump_after_outgoing() -> Weight { - // Minimum execution time: 9_499 nanoseconds. - Weight::from_ref_time(9_800_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 9_674 nanoseconds. + Weight::from_ref_time(9_964_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) } // Storage: Ump Overweight (r:1 w:1) + // Storage: Ump CounterForOverweight (r:1 w:1) fn service_overweight() -> Weight { - // Minimum execution time: 26_656 nanoseconds. - Weight::from_ref_time(27_122_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 29_605 nanoseconds. + Weight::from_ref_time(30_173_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } } diff --git a/runtime/westend/src/weights/xcm/mod.rs b/runtime/westend/src/weights/xcm/mod.rs index 81a657fc2ff7..26df93378959 100644 --- a/runtime/westend/src/weights/xcm/mod.rs +++ b/runtime/westend/src/weights/xcm/mod.rs @@ -5,7 +5,7 @@ use crate::Runtime; use frame_support::weights::Weight; use sp_std::prelude::*; use xcm::{ - latest::{prelude::*, Weight as XCMWeight}, + latest::{prelude::*, QueryResponseInfo}, DoubleEncoded, }; @@ -31,15 +31,15 @@ impl From<&MultiAsset> for AssetTypes { } trait WeighMultiAssets { - fn weigh_multi_assets(&self, balances_weight: Weight) -> XCMWeight; + fn weigh_multi_assets(&self, balances_weight: Weight) -> Weight; } // Westend only knows about one asset, the balances pallet. const MAX_ASSETS: u32 = 1; impl WeighMultiAssets for MultiAssetFilter { - fn weigh_multi_assets(&self, balances_weight: Weight) -> XCMWeight { - let weight = match self { + fn weigh_multi_assets(&self, balances_weight: Weight) -> Weight { + match self { Self::Definite(assets) => assets .inner() .into_iter() @@ -49,155 +49,214 @@ impl WeighMultiAssets for MultiAssetFilter { AssetTypes::Unknown => Weight::MAX, }) .fold(Weight::zero(), |acc, x| acc.saturating_add(x)), - Self::Wild(_) => balances_weight.saturating_mul(MAX_ASSETS as u64), - }; - - weight.ref_time() + Self::Wild(AllOf { .. } | AllOfCounted { .. }) => balances_weight, + Self::Wild(AllCounted(count)) => balances_weight.saturating_mul(*count as u64), + Self::Wild(All) => balances_weight.saturating_mul(MAX_ASSETS as u64), + } } } impl WeighMultiAssets for MultiAssets { - fn weigh_multi_assets(&self, balances_weight: Weight) -> XCMWeight { - let weight = self - .inner() + fn weigh_multi_assets(&self, balances_weight: Weight) -> Weight { + self.inner() .into_iter() .map(|m| >::from(m)) .map(|t| match t { AssetTypes::Balances => balances_weight, AssetTypes::Unknown => Weight::MAX, }) - .fold(Weight::zero(), |acc, x| acc.saturating_add(x)); - - weight.ref_time() + .fold(Weight::zero(), |acc, x| acc.saturating_add(x)) } } pub struct WestendXcmWeight(core::marker::PhantomData); impl XcmWeightInfo for WestendXcmWeight { - fn withdraw_asset(assets: &MultiAssets) -> XCMWeight { + fn withdraw_asset(assets: &MultiAssets) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::withdraw_asset()) } - fn reserve_asset_deposited(assets: &MultiAssets) -> XCMWeight { + fn reserve_asset_deposited(assets: &MultiAssets) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::reserve_asset_deposited()) } - fn receive_teleported_asset(assets: &MultiAssets) -> XCMWeight { + fn receive_teleported_asset(assets: &MultiAssets) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::receive_teleported_asset()) } - fn query_response(_query_id: &u64, _response: &Response, _max_weight: &u64) -> XCMWeight { - XcmGeneric::::query_response().ref_time() + fn query_response( + _query_id: &u64, + _response: &Response, + _max_weight: &Weight, + _querier: &Option, + ) -> Weight { + XcmGeneric::::query_response() } - fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> XCMWeight { + fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::transfer_asset()) } fn transfer_reserve_asset( assets: &MultiAssets, _dest: &MultiLocation, _xcm: &Xcm<()>, - ) -> XCMWeight { + ) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::transfer_reserve_asset()) } fn transact( - _origin_type: &OriginKind, - _require_weight_at_most: &u64, + _origin_kind: &OriginKind, + _require_weight_at_most: &Weight, _call: &DoubleEncoded, - ) -> XCMWeight { - XcmGeneric::::transact().ref_time() + ) -> Weight { + XcmGeneric::::transact() } fn hrmp_new_channel_open_request( _sender: &u32, _max_message_size: &u32, _max_capacity: &u32, - ) -> XCMWeight { + ) -> Weight { // XCM Executor does not currently support HRMP channel operations - Weight::MAX.ref_time() + Weight::MAX } - fn hrmp_channel_accepted(_recipient: &u32) -> XCMWeight { + fn hrmp_channel_accepted(_recipient: &u32) -> Weight { // XCM Executor does not currently support HRMP channel operations - Weight::MAX.ref_time() + Weight::MAX } - fn hrmp_channel_closing(_initiator: &u32, _sender: &u32, _recipient: &u32) -> XCMWeight { + fn hrmp_channel_closing(_initiator: &u32, _sender: &u32, _recipient: &u32) -> Weight { // XCM Executor does not currently support HRMP channel operations - Weight::MAX.ref_time() + Weight::MAX } - fn clear_origin() -> XCMWeight { - XcmGeneric::::clear_origin().ref_time() + fn clear_origin() -> Weight { + XcmGeneric::::clear_origin() } - fn descend_origin(_who: &InteriorMultiLocation) -> XCMWeight { - XcmGeneric::::descend_origin().ref_time() + fn descend_origin(_who: &InteriorMultiLocation) -> Weight { + XcmGeneric::::descend_origin() } - fn report_error( - _query_id: &QueryId, - _dest: &MultiLocation, - _max_response_weight: &u64, - ) -> XCMWeight { - XcmGeneric::::report_error().ref_time() + fn report_error(_query_repsonse_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_error() } - fn deposit_asset( - assets: &MultiAssetFilter, - _max_assets: &u32, // TODO use max assets? - _dest: &MultiLocation, - ) -> XCMWeight { + fn deposit_asset(assets: &MultiAssetFilter, _dest: &MultiLocation) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::deposit_asset()) } fn deposit_reserve_asset( assets: &MultiAssetFilter, - _max_assets: &u32, // TODO use max assets? _dest: &MultiLocation, _xcm: &Xcm<()>, - ) -> XCMWeight { + ) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets) -> XCMWeight { - Weight::MAX.ref_time() // todo fix + fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets, _maximal: &bool) -> Weight { + // Westend does not currently support exchange asset operations + Weight::MAX } fn initiate_reserve_withdraw( assets: &MultiAssetFilter, _reserve: &MultiLocation, _xcm: &Xcm<()>, - ) -> XCMWeight { + ) -> Weight { assets.weigh_multi_assets(XcmGeneric::::initiate_reserve_withdraw()) } fn initiate_teleport( assets: &MultiAssetFilter, _dest: &MultiLocation, _xcm: &Xcm<()>, - ) -> XCMWeight { + ) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::initiate_teleport()) } - fn query_holding( - _query_id: &u64, - _dest: &MultiLocation, - _assets: &MultiAssetFilter, - _max_response_weight: &u64, - ) -> XCMWeight { - XcmGeneric::::query_holding().ref_time() + fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + XcmGeneric::::report_holding() + } + fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { + XcmGeneric::::buy_execution() + } + fn refund_surplus() -> Weight { + XcmGeneric::::refund_surplus() + } + fn set_error_handler(_xcm: &Xcm) -> Weight { + XcmGeneric::::set_error_handler() + } + fn set_appendix(_xcm: &Xcm) -> Weight { + XcmGeneric::::set_appendix() + } + fn clear_error() -> Weight { + XcmGeneric::::clear_error() + } + fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> Weight { + XcmGeneric::::claim_asset() + } + fn trap(_code: &u64) -> Weight { + XcmGeneric::::trap() + } + fn subscribe_version(_query_id: &QueryId, _max_response_weight: &Weight) -> Weight { + XcmGeneric::::subscribe_version() + } + fn unsubscribe_version() -> Weight { + XcmGeneric::::unsubscribe_version() + } + fn burn_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + } + fn expect_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + } + fn expect_origin(_origin: &Option) -> Weight { + XcmGeneric::::expect_origin() + } + fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { + XcmGeneric::::expect_error() + } + fn query_pallet(_module_name: &Vec, _response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::query_pallet() + } + fn expect_pallet( + _index: &u32, + _name: &Vec, + _module_name: &Vec, + _crate_major: &u32, + _min_crate_minor: &u32, + ) -> Weight { + XcmGeneric::::expect_pallet() + } + fn report_transact_status(_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_transact_status() + } + fn clear_transact_status() -> Weight { + XcmGeneric::::clear_transact_status() + } + fn universal_origin(_: &Junction) -> Weight { + // Westend does not currently support universal origin operations + Weight::MAX + } + fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { + // Westend does not currently support export message operations + Weight::MAX } - fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> XCMWeight { - XcmGeneric::::buy_execution().ref_time() + fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Westend does not currently support asset locking operations + Weight::MAX } - fn refund_surplus() -> XCMWeight { - XcmGeneric::::refund_surplus().ref_time() + fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Westend does not currently support asset locking operations + Weight::MAX } - fn set_error_handler(_xcm: &Xcm) -> XCMWeight { - XcmGeneric::::set_error_handler().ref_time() + fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Westend does not currently support asset locking operations + Weight::MAX } - fn set_appendix(_xcm: &Xcm) -> XCMWeight { - XcmGeneric::::set_appendix().ref_time() + fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Westend does not currently support asset locking operations + Weight::MAX } - fn clear_error() -> XCMWeight { - XcmGeneric::::clear_error().ref_time() + fn set_fees_mode(_: &bool) -> Weight { + XcmGeneric::::set_fees_mode() } - fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> XCMWeight { - XcmGeneric::::claim_asset().ref_time() + fn set_topic(_topic: &[u8; 32]) -> Weight { + XcmGeneric::::set_topic() } - fn trap(_code: &u64) -> XCMWeight { - XcmGeneric::::trap().ref_time() + fn clear_topic() -> Weight { + XcmGeneric::::clear_topic() } - fn subscribe_version(_query_id: &QueryId, _max_response_weight: &u64) -> XCMWeight { - XcmGeneric::::subscribe_version().ref_time() + fn alias_origin(_: &MultiLocation) -> Weight { + // XCM Executor does not currently support alias origin operations + Weight::MAX } - fn unsubscribe_version() -> XCMWeight { - XcmGeneric::::unsubscribe_version().ref_time() + fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + XcmGeneric::::unpaid_execution() } } diff --git a/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 319a772e9785..aed7316cdd28 100644 --- a/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -1,4 +1,4 @@ -// Copyright 2017-2021 Parity Technologies (UK) Ltd. +// Copyright 2021 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -17,23 +17,25 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-12-01, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 128 +//! DATE: 2022-12-12, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 // Executed Command: -// target/release/polkadot +// /home/benchbot/cargo_target_dir/production/polkadot // benchmark -// --chain=westend-dev +// pallet // --steps=50 // --repeat=20 -// --pallet=pallet_xcm_benchmarks::generic // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/polkadot/.git/.artifacts/bench.json +// --pallet=pallet_xcm_benchmarks::generic +// --chain=westend-dev // --header=./file_header.txt // --template=./xcm/pallet-xcm-benchmarks/template.hbs -// --output=./runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +// --output=./runtime/westend/src/weights/xcm/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,85 +50,135 @@ impl WeightInfo { // Storage: XcmPallet SupportedVersion (r:1 w:0) // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) // Storage: XcmPallet SafeXcmVersion (r:1 w:0) - // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) // Storage: Dmp DownwardMessageQueues (r:1 w:1) - pub(crate) fn query_holding() -> Weight { - Weight::from_ref_time(39_278_000 as u64) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + pub(crate) fn report_holding() -> Weight { + Weight::from_ref_time(34_089_000 as u64) .saturating_add(T::DbWeight::get().reads(5 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } pub(crate) fn buy_execution() -> Weight { - Weight::from_ref_time(5_922_000 as u64) + Weight::from_ref_time(5_751_000 as u64) } // Storage: XcmPallet Queries (r:1 w:0) pub(crate) fn query_response() -> Weight { - Weight::from_ref_time(20_625_000 as u64) + Weight::from_ref_time(17_938_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) } pub(crate) fn transact() -> Weight { - Weight::from_ref_time(22_198_000 as u64) + Weight::from_ref_time(20_699_000 as u64) } pub(crate) fn refund_surplus() -> Weight { - Weight::from_ref_time(6_122_000 as u64) + Weight::from_ref_time(6_077_000 as u64) } pub(crate) fn set_error_handler() -> Weight { - Weight::from_ref_time(5_758_000 as u64) + Weight::from_ref_time(5_747_000 as u64) } pub(crate) fn set_appendix() -> Weight { - Weight::from_ref_time(5_764_000 as u64) + Weight::from_ref_time(5_837_000 as u64) } pub(crate) fn clear_error() -> Weight { - Weight::from_ref_time(5_679_000 as u64) + Weight::from_ref_time(5_712_000 as u64) } pub(crate) fn descend_origin() -> Weight { - Weight::from_ref_time(7_206_000 as u64) + Weight::from_ref_time(6_471_000 as u64) } pub(crate) fn clear_origin() -> Weight { - Weight::from_ref_time(5_738_000 as u64) + Weight::from_ref_time(5_725_000 as u64) } // Storage: XcmPallet SupportedVersion (r:1 w:0) // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) // Storage: XcmPallet SafeXcmVersion (r:1 w:0) - // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) pub(crate) fn report_error() -> Weight { - Weight::from_ref_time(31_512_000 as u64) + Weight::from_ref_time(29_975_000 as u64) .saturating_add(T::DbWeight::get().reads(5 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: XcmPallet AssetTraps (r:1 w:1) pub(crate) fn claim_asset() -> Weight { - Weight::from_ref_time(13_594_000 as u64) + Weight::from_ref_time(21_598_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } pub(crate) fn trap() -> Weight { - Weight::from_ref_time(5_745_000 as u64) + Weight::from_ref_time(5_665_000 as u64) } // Storage: XcmPallet VersionNotifyTargets (r:1 w:1) // Storage: XcmPallet SupportedVersion (r:1 w:0) // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) // Storage: XcmPallet SafeXcmVersion (r:1 w:0) - // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) pub(crate) fn subscribe_version() -> Weight { - Weight::from_ref_time(38_138_000 as u64) + Weight::from_ref_time(38_343_000 as u64) .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: XcmPallet VersionNotifyTargets (r:0 w:1) pub(crate) fn unsubscribe_version() -> Weight { - Weight::from_ref_time(9_127_000 as u64) + Weight::from_ref_time(8_353_000 as u64) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: XcmPallet SupportedVersion (r:1 w:0) // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) // Storage: XcmPallet SafeXcmVersion (r:1 w:0) - // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) pub(crate) fn initiate_reserve_withdraw() -> Weight { - Weight::from_ref_time(41_443_000 as u64) + Weight::from_ref_time(33_100_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + pub(crate) fn burn_asset() -> Weight { + Weight::from_ref_time(7_259_000 as u64) + } + pub(crate) fn expect_asset() -> Weight { + Weight::from_ref_time(5_848_000 as u64) + } + pub(crate) fn expect_origin() -> Weight { + Weight::from_ref_time(5_787_000 as u64) + } + pub(crate) fn expect_error() -> Weight { + Weight::from_ref_time(5_775_000 as u64) + } + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + pub(crate) fn query_pallet() -> Weight { + Weight::from_ref_time(34_846_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + pub(crate) fn expect_pallet() -> Weight { + Weight::from_ref_time(8_844_000 as u64) + } + // Storage: XcmPallet SupportedVersion (r:1 w:0) + // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) + // Storage: XcmPallet SafeXcmVersion (r:1 w:0) + // Storage: Dmp DownwardMessageQueues (r:1 w:1) + // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) + pub(crate) fn report_transact_status() -> Weight { + Weight::from_ref_time(50_256_000 as u64) .saturating_add(T::DbWeight::get().reads(5 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } + pub(crate) fn clear_transact_status() -> Weight { + Weight::from_ref_time(9_959_000 as u64) + } + pub(crate) fn set_topic() -> Weight { + Weight::from_ref_time(10_007_000 as u64) + } + pub(crate) fn clear_topic() -> Weight { + Weight::from_ref_time(8_289_000 as u64) + } + pub(crate) fn set_fees_mode() -> Weight { + Weight::from_ref_time(5_764_000 as u64) + } + pub(crate) fn unpaid_execution() -> Weight { + Weight::from_ref_time(5_924_000 as u64) + } } diff --git a/runtime/westend/src/xcm_config.rs b/runtime/westend/src/xcm_config.rs index a1e108eb3be0..5e887dafe9a1 100644 --- a/runtime/westend/src/xcm_config.rs +++ b/runtime/westend/src/xcm_config.rs @@ -17,52 +17,54 @@ //! XCM configurations for Westend. use super::{ - parachains_origin, weights, AccountId, Balances, ParaId, Runtime, RuntimeCall, RuntimeEvent, - RuntimeOrigin, WeightToFee, XcmPallet, + parachains_origin, weights, AccountId, AllPalletsWithSystem, Balances, ParaId, Runtime, + RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmPallet, }; use frame_support::{ parameter_types, - traits::{Everything, Nothing}, + traits::{Contains, Everything, Nothing}, }; use runtime_common::{xcm_sender, ToAuthor}; +use sp_core::ConstU32; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, - AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, ChildParachainAsNative, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - CurrencyAdapter as XcmCurrencyAdapter, IsChildSystemParachain, IsConcrete, LocationInverter, + CurrencyAdapter as XcmCurrencyAdapter, IsChildSystemParachain, IsConcrete, MintLocation, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - UsingComponents, WeightInfoBounds, + UsingComponents, WeightInfoBounds, WithComputedOrigin, }; +use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; parameter_types! { - pub const WndLocation: MultiLocation = Here.into(); - pub const Ancestry: MultiLocation = Here.into(); - pub WestendNetwork: NetworkId = - NetworkId::Named(b"Westend".to_vec().try_into().expect("shorter than length limit; qed")); + pub const TokenLocation: MultiLocation = Here.into_location(); + pub const ThisNetwork: NetworkId = Westend; + pub UniversalLocation: InteriorMultiLocation = ThisNetwork::get().into(); pub CheckAccount: AccountId = XcmPallet::check_account(); + pub LocalCheckAccount: (AccountId, MintLocation) = (CheckAccount::get(), MintLocation::Local); } pub type LocationConverter = - (ChildParachainConvertsVia, AccountId32Aliases); + (ChildParachainConvertsVia, AccountId32Aliases); pub type LocalAssetTransactor = XcmCurrencyAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, + IsConcrete, // We can convert the MultiLocations with our converter above: LocationConverter, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, // It's a native asset so we keep track of the teleports to maintain total issuance. - CheckAccount, + LocalCheckAccount, >; type LocalOriginConverter = ( SovereignSignedViaLocation, ChildParachainAsNative, - SignedAccountId32AsNative, + SignedAccountId32AsNative, ChildSystemParachainAsSuperuser, ); @@ -70,35 +72,143 @@ type LocalOriginConverter = ( /// individual routers. pub type XcmRouter = ( // Only one router so far - use DMP to communicate with child parachains. - xcm_sender::ChildParachainRouter, + xcm_sender::ChildParachainRouter, ); parameter_types! { - pub const Westmint: MultiLocation = Parachain(1000).into(); - pub const Collectives: MultiLocation = Parachain(1001).into(); - pub const WestendForWestmint: (MultiAssetFilter, MultiLocation) = - (Wild(AllOf { fun: WildFungible, id: Concrete(WndLocation::get()) }), Westmint::get()); - pub const WestendForCollectives: (MultiAssetFilter, MultiLocation) = - (Wild(AllOf { fun: WildFungible, id: Concrete(WndLocation::get()) }), Collectives::get()); + pub const Westmint: MultiLocation = Parachain(1000).into_location(); + pub const Collectives: MultiLocation = Parachain(1001).into_location(); + pub const Wnd: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete(TokenLocation::get()) }); + pub const WndForWestmint: (MultiAssetFilter, MultiLocation) = (Wnd::get(), Westmint::get()); + pub const WndForCollectives: (MultiAssetFilter, MultiLocation) = (Wnd::get(), Collectives::get()); pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Parachain(1000).into()); +} + pub type TrustedTeleporters = - (xcm_builder::Case, xcm_builder::Case); + (xcm_builder::Case, xcm_builder::Case); /// The barriers one of which must be passed for an XCM message to be executed. pub type Barrier = ( // Weight that is paid for may be consumed. TakeWeightCredit, - // If the message is one that immediately attemps to pay for execution, then allow it. - AllowTopLevelPaidExecutionFrom, - // Messages coming from system parachains need not pay for execution. - AllowUnpaidExecutionFrom>, // Expected responses are OK. AllowKnownQueryResponses, - // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, + WithComputedOrigin< + ( + // If the message is one that immediately attemps to pay for execution, then allow it. + AllowTopLevelPaidExecutionFrom, + // Messages coming from system parachains need not pay for execution. + AllowExplicitUnpaidExecutionFrom>, + // Subscriptions for version tracking are OK. + AllowSubscriptionsFrom, + ), + UniversalLocation, + ConstU32<8>, + >, ); +/// A call filter for the XCM Transact instruction. This is a temporary measure until we +/// properly account for proof size weights. +/// +/// Calls that are allowed through this filter must: +/// 1. Have a fixed weight; +/// 2. Cannot lead to another call being made; +/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. +pub struct SafeCallFilter; +impl Contains for SafeCallFilter { + fn contains(call: &RuntimeCall) -> bool { + #[cfg(feature = "runtime-benchmarks")] + { + if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { + return true + } + } + + match call { + RuntimeCall::System( + frame_system::Call::kill_prefix { .. } | frame_system::Call::set_heap_pages { .. }, + ) | + RuntimeCall::Babe(..) | + RuntimeCall::Timestamp(..) | + RuntimeCall::Indices(..) | + RuntimeCall::Balances(..) | + RuntimeCall::Staking( + pallet_staking::Call::bond { .. } | + pallet_staking::Call::bond_extra { .. } | + pallet_staking::Call::unbond { .. } | + pallet_staking::Call::withdraw_unbonded { .. } | + pallet_staking::Call::validate { .. } | + pallet_staking::Call::nominate { .. } | + pallet_staking::Call::chill { .. } | + pallet_staking::Call::set_payee { .. } | + pallet_staking::Call::set_controller { .. } | + pallet_staking::Call::set_validator_count { .. } | + pallet_staking::Call::increase_validator_count { .. } | + pallet_staking::Call::scale_validator_count { .. } | + pallet_staking::Call::force_no_eras { .. } | + pallet_staking::Call::force_new_era { .. } | + pallet_staking::Call::set_invulnerables { .. } | + pallet_staking::Call::force_unstake { .. } | + pallet_staking::Call::force_new_era_always { .. } | + pallet_staking::Call::payout_stakers { .. } | + pallet_staking::Call::rebond { .. } | + pallet_staking::Call::reap_stash { .. } | + pallet_staking::Call::set_staking_configs { .. } | + pallet_staking::Call::chill_other { .. } | + pallet_staking::Call::force_apply_min_commission { .. }, + ) | + RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | + RuntimeCall::Grandpa(..) | + RuntimeCall::ImOnline(..) | + RuntimeCall::Utility(pallet_utility::Call::as_derivative { .. }) | + RuntimeCall::Identity( + pallet_identity::Call::add_registrar { .. } | + pallet_identity::Call::set_identity { .. } | + pallet_identity::Call::clear_identity { .. } | + pallet_identity::Call::request_judgement { .. } | + pallet_identity::Call::cancel_request { .. } | + pallet_identity::Call::set_fee { .. } | + pallet_identity::Call::set_account_id { .. } | + pallet_identity::Call::set_fields { .. } | + pallet_identity::Call::provide_judgement { .. } | + pallet_identity::Call::kill_identity { .. } | + pallet_identity::Call::add_sub { .. } | + pallet_identity::Call::rename_sub { .. } | + pallet_identity::Call::remove_sub { .. } | + pallet_identity::Call::quit_sub { .. }, + ) | + RuntimeCall::Recovery(..) | + RuntimeCall::Vesting(..) | + RuntimeCall::ElectionProviderMultiPhase(..) | + RuntimeCall::VoterList(..) | + RuntimeCall::NominationPools( + pallet_nomination_pools::Call::join { .. } | + pallet_nomination_pools::Call::bond_extra { .. } | + pallet_nomination_pools::Call::claim_payout { .. } | + pallet_nomination_pools::Call::unbond { .. } | + pallet_nomination_pools::Call::pool_withdraw_unbonded { .. } | + pallet_nomination_pools::Call::withdraw_unbonded { .. } | + pallet_nomination_pools::Call::create { .. } | + pallet_nomination_pools::Call::create_with_pool_id { .. } | + pallet_nomination_pools::Call::set_state { .. } | + pallet_nomination_pools::Call::set_configs { .. } | + pallet_nomination_pools::Call::update_roles { .. } | + pallet_nomination_pools::Call::chill { .. }, + ) | + RuntimeCall::XcmPallet(pallet_xcm::Call::limited_reserve_transfer_assets { + .. + }) => true, + _ => false, + } + } +} + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -107,22 +217,32 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = LocalOriginConverter; type IsReserve = (); type IsTeleporter = TrustedTeleporters; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = WeightInfoBounds, RuntimeCall, MaxInstructions>; - type Trader = UsingComponents>; + type Trader = + UsingComponents>; type ResponseHandler = XcmPallet; type AssetTrap = XcmPallet; + type AssetLocker = (); + type AssetExchanger = (); type AssetClaims = XcmPallet; type SubscriptionService = XcmPallet; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = WithOriginFilter; + type SafeCallFilter = SafeCallFilter; } /// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior location /// of this chain. pub type LocalOriginToLocation = ( // And a usual Signed origin to be used in XCM as a corresponding AccountId32 - SignedToAccountId32, + SignedToAccountId32, ); impl pallet_xcm::Config for Runtime { @@ -133,14 +253,22 @@ impl pallet_xcm::Config for Runtime { type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; // ...but they must match our filter, which rejects everything. type XcmExecuteFilter = Nothing; - type XcmExecutor = xcm_executor::XcmExecutor; + type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = WeightInfoBounds, RuntimeCall, MaxInstructions>; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = IsConcrete; + type TrustedLockers = (); + type SovereignAccountOf = LocationConverter; + type MaxLockers = ConstU32<8>; + type WeightInfo = crate::weights::pallet_xcm::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; } diff --git a/scripts/ci/gitlab/lingua.dic b/scripts/ci/gitlab/lingua.dic index a2f64af1cbd6..d9dad4540277 100644 --- a/scripts/ci/gitlab/lingua.dic +++ b/scripts/ci/gitlab/lingua.dic @@ -163,6 +163,7 @@ MQC/SM msg multisig/S multivalidator/SM +mutators mutex natively NFA @@ -220,6 +221,7 @@ proxy/G proxying PRs PVF/S +querier README/MS redhat/M register/CD @@ -254,6 +256,7 @@ SS58 SSL startup/MS stateful +Statemine str struct/MS subcommand/SM @@ -291,6 +294,7 @@ UI unapplied unassign unconcluded +unexpectable unfinalize/B unfinalized union/MSG diff --git a/xcm/Cargo.toml b/xcm/Cargo.toml index 4b653131b77b..d66e25748bce 100644 --- a/xcm/Cargo.toml +++ b/xcm/Cargo.toml @@ -6,22 +6,26 @@ authors.workspace = true edition.workspace = true [dependencies] +derivative = { version = "2.2.0", default-features = false, features = [ "use_core" ] } impl-trait-for-tuples = "0.2.2" -parity-scale-codec = { version = "3.1.5", default-features = false, features = ["derive", "max-encoded-len"] } -scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } -derivative = {version = "2.2.0", default-features = false, features = [ "use_core" ] } log = { version = "0.4.17", default-features = false } +parity-scale-codec = { version = "3.1.5", default-features = false, features = [ "derive", "max-encoded-len" ] } +scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-weights = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +serde = { version = "1.0.136", optional = true, features = ["derive"] } xcm-procedural = { path = "procedural" } +[dev-dependencies] +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } + [features] default = ["std"] wasm-api = [] -runtime-benchmarks = [ - "sp-runtime/runtime-benchmarks", -] std = [ "parity-scale-codec/std", "scale-info/std", - "sp-runtime/std", + "serde", + "sp-core/std", + "sp-weights/std", ] diff --git a/xcm/pallet-xcm-benchmarks/Cargo.toml b/xcm/pallet-xcm-benchmarks/Cargo.toml index 0cbcba63bf43..6999f6aaffb2 100644 --- a/xcm/pallet-xcm-benchmarks/Cargo.toml +++ b/xcm/pallet-xcm-benchmarks/Cargo.toml @@ -14,18 +14,18 @@ frame-support = { default-features = false, branch = "master", git = "https://gi frame-system = { default-features = false, branch = "master", git = "https://github.com/paritytech/substrate" } sp-runtime = { default-features = false, branch = "master", git = "https://github.com/paritytech/substrate" } sp-std = { default-features = false, branch = "master", git = "https://github.com/paritytech/substrate" } +sp-io = { default-features = false, branch = "master", git = "https://github.com/paritytech/substrate" } xcm-executor = { path = "../xcm-executor", default-features = false } frame-benchmarking = { default-features = false, branch = "master", git = "https://github.com/paritytech/substrate" } xcm = { path = "..", default-features = false } +xcm-builder = { path = "../xcm-builder", default-features = false } log = "0.4.17" [dev-dependencies] pallet-balances = { branch = "master", git = "https://github.com/paritytech/substrate" } pallet-assets = { branch = "master", git = "https://github.com/paritytech/substrate" } sp-core = { branch = "master", git = "https://github.com/paritytech/substrate" } -sp-io = { branch = "master", git = "https://github.com/paritytech/substrate" } sp-tracing = { branch = "master", git = "https://github.com/paritytech/substrate" } -xcm-builder = { path = "../xcm-builder" } xcm = { path = ".." } # temp pallet-xcm = { path = "../pallet-xcm" } @@ -40,11 +40,14 @@ std = [ "frame-benchmarking/std", "frame-support/std", "frame-system/std", + "sp-io/std", "sp-runtime/std", - "sp-std/std" + "sp-std/std", + "xcm-builder/std", + "xcm-executor/std" ] runtime-benchmarks = [ - "xcm/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", diff --git a/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs b/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs index d97ac34b2651..fade0d4869b3 100644 --- a/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs +++ b/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs @@ -41,23 +41,31 @@ benchmarks_instance_pallet! { withdraw_asset { let (sender_account, sender_location) = account_and_location::(1); - let worst_case_holding = T::worst_case_holding(); + let worst_case_holding = T::worst_case_holding(0); let asset = T::get_multi_asset(); - >::deposit_asset(&asset, &sender_location).unwrap(); + >::deposit_asset( + &asset, + &sender_location, + &XcmContext { + origin: Some(sender_location.clone()), + message_hash: [0; 32], + topic: None, + }, + ).unwrap(); // check the assets of origin. assert!(!T::TransactAsset::balance(&sender_account).is_zero()); let mut executor = new_executor::(sender_location); - executor.holding = worst_case_holding.into(); + executor.set_holding(worst_case_holding.into()); let instruction = Instruction::>::WithdrawAsset(vec![asset.clone()].into()); let xcm = Xcm(vec![instruction]); }: { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { // check one of the assets of origin. assert!(T::TransactAsset::balance(&sender_account).is_zero()); - assert!(executor.holding.ensure_contains(&vec![asset].into()).is_ok()); + assert!(executor.holding().ensure_contains(&vec![asset].into()).is_ok()); } transfer_asset { @@ -69,14 +77,22 @@ benchmarks_instance_pallet! { let dest_location = T::valid_destination()?; let dest_account = T::AccountIdConverter::convert(dest_location.clone()).unwrap(); - >::deposit_asset(&asset, &sender_location).unwrap(); + >::deposit_asset( + &asset, + &sender_location, + &XcmContext { + origin: Some(sender_location.clone()), + message_hash: [0; 32], + topic: None, + }, + ).unwrap(); assert!(T::TransactAsset::balance(&dest_account).is_zero()); let mut executor = new_executor::(sender_location); let instruction = Instruction::TransferAsset { assets, beneficiary: dest_location }; let xcm = Xcm(vec![instruction]); }: { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { assert!(T::TransactAsset::balance(&sender_account).is_zero()); assert!(!T::TransactAsset::balance(&dest_account).is_zero()); @@ -88,7 +104,15 @@ benchmarks_instance_pallet! { let dest_account = T::AccountIdConverter::convert(dest_location.clone()).unwrap(); let asset = T::get_multi_asset(); - >::deposit_asset(&asset, &sender_location).unwrap(); + >::deposit_asset( + &asset, + &sender_location, + &XcmContext { + origin: Some(sender_location.clone()), + message_hash: [0; 32], + topic: None, + }, + ).unwrap(); let assets: MultiAssets = vec![ asset ].into(); assert!(T::TransactAsset::balance(&dest_account).is_zero()); @@ -100,38 +124,19 @@ benchmarks_instance_pallet! { }; let xcm = Xcm(vec![instruction]); }: { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { assert!(T::TransactAsset::balance(&sender_account).is_zero()); assert!(!T::TransactAsset::balance(&dest_account).is_zero()); // TODO: Check sender queue is not empty. #4426 } - reserve_asset_deposited { - let (trusted_reserve, transferable_reserve_asset) = T::TrustedReserve::get() - .ok_or(BenchmarkError::Skip)?; - - let assets: MultiAssets = vec![ transferable_reserve_asset ].into(); - - let mut executor = new_executor::(trusted_reserve); - let instruction = Instruction::ReserveAssetDeposited(assets.clone()); - let xcm = Xcm(vec![instruction]); - }: { - executor.execute(xcm).map_err(|_| { - BenchmarkError::Override( - BenchmarkResult::from_weight(T::BlockWeights::get().max_block) - ) - })?; - } verify { - assert!(executor.holding.ensure_contains(&assets).is_ok()); - } - receive_teleported_asset { // If there is no trusted teleporter, then we skip this benchmark. let (trusted_teleporter, teleportable_asset) = T::TrustedTeleporter::get() .ok_or(BenchmarkError::Skip)?; - if let Some(checked_account) = T::CheckedAccount::get() { + if let Some((checked_account, _)) = T::CheckedAccount::get() { T::TransactAsset::mint_into( &checked_account, < @@ -148,18 +153,18 @@ benchmarks_instance_pallet! { let instruction = Instruction::ReceiveTeleportedAsset(assets.clone()); let xcm = Xcm(vec![instruction]); }: { - executor.execute(xcm).map_err(|_| { + executor.bench_process(xcm).map_err(|_| { BenchmarkError::Override( BenchmarkResult::from_weight(T::BlockWeights::get().max_block) ) })?; } verify { - assert!(executor.holding.ensure_contains(&assets).is_ok()); + assert!(executor.holding().ensure_contains(&assets).is_ok()); } deposit_asset { let asset = T::get_multi_asset(); - let mut holding = T::worst_case_holding(); + let mut holding = T::worst_case_holding(1); // Add our asset to the holding. holding.push(asset.clone()); @@ -170,15 +175,14 @@ benchmarks_instance_pallet! { assert!(T::TransactAsset::balance(&dest_account).is_zero()); let mut executor = new_executor::(Default::default()); - executor.holding = holding.into(); + executor.set_holding(holding.into()); let instruction = Instruction::>::DepositAsset { assets: asset.into(), - max_assets: 1, beneficiary: dest_location, }; let xcm = Xcm(vec![instruction]); }: { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { // dest should have received some asset. assert!(!T::TransactAsset::balance(&dest_account).is_zero()) @@ -186,7 +190,7 @@ benchmarks_instance_pallet! { deposit_reserve_asset { let asset = T::get_multi_asset(); - let mut holding = T::worst_case_holding(); + let mut holding = T::worst_case_holding(1); // Add our asset to the holding. holding.push(asset.clone()); @@ -197,16 +201,15 @@ benchmarks_instance_pallet! { assert!(T::TransactAsset::balance(&dest_account).is_zero()); let mut executor = new_executor::(Default::default()); - executor.holding = holding.into(); + executor.set_holding(holding.into()); let instruction = Instruction::>::DepositReserveAsset { assets: asset.into(), - max_assets: 1, dest: dest_location, xcm: Xcm::new(), }; let xcm = Xcm(vec![instruction]); }: { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { // dest should have received some asset. assert!(!T::TransactAsset::balance(&dest_account).is_zero()) @@ -214,16 +217,16 @@ benchmarks_instance_pallet! { initiate_teleport { let asset = T::get_multi_asset(); - let mut holding = T::worst_case_holding(); + let mut holding = T::worst_case_holding(0); // Add our asset to the holding. holding.push(asset.clone()); // Checked account starts at zero - assert!(T::CheckedAccount::get().map_or(true, |c| T::TransactAsset::balance(&c).is_zero())); + assert!(T::CheckedAccount::get().map_or(true, |(c, _)| T::TransactAsset::balance(&c).is_zero())); let mut executor = new_executor::(Default::default()); - executor.holding = holding.into(); + executor.set_holding(holding.into()); let instruction = Instruction::>::InitiateTeleport { assets: asset.into(), dest: T::valid_destination()?, @@ -231,9 +234,9 @@ benchmarks_instance_pallet! { }; let xcm = Xcm(vec![instruction]); }: { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { - if let Some(checked_account) = T::CheckedAccount::get() { + if let Some((checked_account, _)) = T::CheckedAccount::get() { // teleport checked account should have received some asset. assert!(!T::TransactAsset::balance(&checked_account).is_zero()); } diff --git a/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs b/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs index 3722c7602a84..e3a02a9bcf94 100644 --- a/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs +++ b/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs @@ -18,7 +18,11 @@ use crate::{fungible as xcm_balances_benchmark, mock::*}; use frame_benchmarking::BenchmarkError; -use frame_support::{parameter_types, traits::Everything, weights::Weight}; +use frame_support::{ + parameter_types, + traits::{Everything, Nothing}, + weights::Weight, +}; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -26,7 +30,7 @@ use sp_runtime::{ BuildStorage, }; use xcm::latest::prelude::*; -use xcm_builder::AllowUnpaidExecutionFrom; +use xcm_builder::{AllowUnpaidExecutionFrom, MintLocation}; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -47,9 +51,7 @@ frame_support::construct_runtime!( parameter_types! { pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max( - Weight::from_ref_time(1024).set_proof_size(u64::MAX), - ); + frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, u64::MAX)); } impl frame_system::Config for Test { type BaseCallFilter = Everything; @@ -119,13 +121,14 @@ pub type AssetTransactor = xcm_builder::CurrencyAdapter< MatchAnyFungible, AccountIdConverter, u64, - CheckedAccount, + CheckingAccount, >; parameter_types! { /// Maximum number of instructions in a single XCM fragment. A sanity check against weight /// calculations getting too crazy. pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } pub struct XcmConfig; @@ -134,16 +137,25 @@ impl xcm_executor::Config for XcmConfig { type XcmSender = DevNull; type AssetTransactor = AssetTransactor; type OriginConverter = (); - type IsReserve = TrustedReserves; + type IsReserve = (); type IsTeleporter = TrustedTeleporters; - type LocationInverter = xcm_builder::LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = AllowUnpaidExecutionFrom; type Weigher = xcm_builder::FixedWeightBounds; type Trader = xcm_builder::FixedRateOfFungible; type ResponseHandler = DevNull; type AssetTrap = (); + type AssetLocker = (); + type AssetExchanger = (); type AssetClaims = (); type SubscriptionService = (); + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; } impl crate::Config for Test { @@ -151,40 +163,37 @@ impl crate::Config for Test { type AccountIdConverter = AccountIdConverter; fn valid_destination() -> Result { let valid_destination: MultiLocation = - X1(AccountId32 { network: NetworkId::Any, id: [0u8; 32] }).into(); + X1(AccountId32 { network: None, id: [0u8; 32] }).into(); Ok(valid_destination) } - fn worst_case_holding() -> MultiAssets { - crate::mock_worst_case_holding() + fn worst_case_holding(depositable_count: u32) -> MultiAssets { + crate::mock_worst_case_holding( + depositable_count, + ::MaxAssetsIntoHolding::get(), + ) } } -pub type TrustedTeleporters = (xcm_builder::Case,); -pub type TrustedReserves = (xcm_builder::Case,); +pub type TrustedTeleporters = (xcm_builder::Case,); parameter_types! { - pub const CheckedAccount: Option = Some(100); - pub const ChildTeleporter: MultiLocation = Parachain(1000).into(); + pub const CheckingAccount: Option<(u64, MintLocation)> = Some((100, MintLocation::Local)); + pub const ChildTeleporter: MultiLocation = Parachain(1000).into_location(); pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( ChildTeleporter::get(), - MultiAsset { id: Concrete(Here.into()), fun: Fungible(100) }, - )); - pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = Some(( - ChildTeleporter::get(), - MultiAsset { id: Concrete(Here.into()), fun: Fungible(100) }, + MultiAsset { id: Concrete(Here.into_location()), fun: Fungible(100) }, )); - pub const TeleConcreteFung: (MultiAssetFilter, MultiLocation) = - (Wild(AllOf { fun: WildFungible, id: Concrete(Here.into()) }), ChildTeleporter::get()); - pub const RsrvConcreteFung: (MultiAssetFilter, MultiLocation) = - (Wild(AllOf { fun: WildFungible, id: Concrete(Here.into()) }), ChildTeleporter::get()); + pub const TeleportConcreteFungible: (MultiAssetFilter, MultiLocation) = + (Wild(AllOf { fun: WildFungible, id: Concrete(Here.into_location()) }), ChildTeleporter::get()); + pub const ReserveConcreteFungible: (MultiAssetFilter, MultiLocation) = + (Wild(AllOf { fun: WildFungible, id: Concrete(Here.into_location()) }), ChildTeleporter::get()); } impl xcm_balances_benchmark::Config for Test { type TransactAsset = Balances; - type CheckedAccount = CheckedAccount; + type CheckedAccount = CheckingAccount; type TrustedTeleporter = TrustedTeleporter; - type TrustedReserve = TrustedReserve; fn get_multi_asset() -> MultiAsset { let amount = diff --git a/xcm/pallet-xcm-benchmarks/src/fungible/mod.rs b/xcm/pallet-xcm-benchmarks/src/fungible/mod.rs index a7fd0e99fd8e..011e84a0e42e 100644 --- a/xcm/pallet-xcm-benchmarks/src/fungible/mod.rs +++ b/xcm/pallet-xcm-benchmarks/src/fungible/mod.rs @@ -34,15 +34,11 @@ pub mod pallet { type TransactAsset: frame_support::traits::fungible::Mutate; /// The account used to check assets being teleported. - type CheckedAccount: Get>; + type CheckedAccount: Get>; /// A trusted location which we allow teleports from, and the asset we allow to teleport. type TrustedTeleporter: Get>; - /// A trusted location where reserve assets are stored, and the asset we allow to be - /// reserves. - type TrustedReserve: Get>; - /// Give me a fungible asset that your asset transactor is going to accept. fn get_multi_asset() -> xcm::latest::MultiAsset; } diff --git a/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs b/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs index aed6140b3a4b..797238babe20 100644 --- a/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs +++ b/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs @@ -20,27 +20,33 @@ use codec::Encode; use frame_benchmarking::{benchmarks, BenchmarkError}; use frame_support::dispatch::GetDispatchInfo; use sp_std::vec; -use xcm::{latest::prelude::*, DoubleEncoded}; +use xcm::{ + latest::{prelude::*, MaybeErrorCode, Weight}, + DoubleEncoded, +}; +use xcm_executor::{ExecutorError, FeesMode}; benchmarks! { - query_holding { - let holding = T::worst_case_holding(); + report_holding { + let holding = T::worst_case_holding(0); let mut executor = new_executor::(Default::default()); - executor.holding = holding.clone().into(); + executor.set_holding(holding.clone().into()); - let instruction = Instruction::>::QueryHolding { - query_id: Default::default(), - dest: T::valid_destination()?, + let instruction = Instruction::>::ReportHolding { + response_info: QueryResponseInfo { + destination: T::valid_destination()?, + query_id: Default::default(), + max_weight: Weight::MAX, + }, // Worst case is looking through all holdings for every asset explicitly. assets: Definite(holding), - max_response_weight: u64::MAX, }; let xcm = Xcm(vec![instruction]); } : { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { // The completion of execution above is enough to validate this is completed. } @@ -48,21 +54,21 @@ benchmarks! { // This benchmark does not use any additional orders or instructions. This should be managed // by the `deep` and `shallow` implementation. buy_execution { - let holding = T::worst_case_holding().into(); + let holding = T::worst_case_holding(0).into(); let mut executor = new_executor::(Default::default()); - executor.holding = holding; + executor.set_holding(holding); let fee_asset = Concrete(Here.into()); let instruction = Instruction::>::BuyExecution { - fees: (fee_asset, 100_000_000).into(), // should be something inside of holding + fees: (fee_asset, 100_000_000u128).into(), // should be something inside of holding weight_limit: WeightLimit::Unlimited, }; let xcm = Xcm(vec![instruction]); } : { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { } @@ -70,11 +76,12 @@ benchmarks! { query_response { let mut executor = new_executor::(Default::default()); let (query_id, response) = T::worst_case_response(); - let max_weight = u64::MAX; - let instruction = Instruction::QueryResponse { query_id, response, max_weight }; + let max_weight = Weight::MAX; + let querier: Option = Some(Here.into()); + let instruction = Instruction::QueryResponse { query_id, response, max_weight, querier }; let xcm = Xcm(vec![instruction]); }: { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { // The assert above is enough to show this XCM succeeded } @@ -83,43 +90,38 @@ benchmarks! { // and included in the final weight calculation. So this is just the overhead of submitting // a noop call. transact { - let origin = T::transact_origin()?; + let (origin, noop_call) = T::transact_origin_and_runtime_call()?; let mut executor = new_executor::(origin); - let noop_call: ::RuntimeCall = frame_system::Call::remark_with_event { - remark: Default::default() - }.into(); let double_encoded_noop_call: DoubleEncoded<_> = noop_call.encode().into(); let instruction = Instruction::Transact { - origin_type: OriginKind::SovereignAccount, - require_weight_at_most: noop_call.get_dispatch_info().weight.ref_time(), + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: noop_call.get_dispatch_info().weight, call: double_encoded_noop_call, }; let xcm = Xcm(vec![instruction]); let num_events = frame_system::Pallet::::events().len(); }: { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { - // TODO make better assertion? #4426 - let num_events2 = frame_system::Pallet::::events().len(); - assert_eq!(num_events + 1, num_events2); + // TODO Make the assertion configurable? } refund_surplus { - let holding = T::worst_case_holding().into(); + let holding = T::worst_case_holding(0).into(); let mut executor = new_executor::(Default::default()); - executor.holding = holding; - executor.total_surplus = 1337; - executor.total_refunded = 0; + executor.set_holding(holding); + executor.set_total_surplus(Weight::from_parts(1337, 1337)); + executor.set_total_refunded(Weight::zero()); let instruction = Instruction::>::RefundSurplus; let xcm = Xcm(vec![instruction]); } : { - let result = executor.execute(xcm)?; + let result = executor.bench_process(xcm)?; } verify { - assert_eq!(executor.total_surplus, 1337); - assert_eq!(executor.total_refunded, 1337); + assert_eq!(executor.total_surplus(), &Weight::from_parts(1337, 1337)); + assert_eq!(executor.total_refunded(), &Weight::from_parts(1337, 1337)); } set_error_handler { @@ -127,9 +129,9 @@ benchmarks! { let instruction = Instruction::>::SetErrorHandler(Xcm(vec![])); let xcm = Xcm(vec![instruction]); } : { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { - assert_eq!(executor.error_handler, Xcm(vec![])); + assert_eq!(executor.error_handler(), &Xcm(vec![])); } set_appendix { @@ -138,20 +140,20 @@ benchmarks! { let instruction = Instruction::>::SetAppendix(appendix); let xcm = Xcm(vec![instruction]); } : { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { - assert_eq!(executor.appendix, Xcm(vec![])); + assert_eq!(executor.appendix(), &Xcm(vec![])); } clear_error { let mut executor = new_executor::(Default::default()); - executor.error = Some((5u32, XcmError::Overflow)); + executor.set_error(Some((5u32, XcmError::Overflow))); let instruction = Instruction::>::ClearError; let xcm = Xcm(vec![instruction]); } : { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { - assert!(executor.error.is_none()) + assert!(executor.error().is_none()) } descend_origin { @@ -160,11 +162,11 @@ benchmarks! { let instruction = Instruction::DescendOrigin(who.clone()); let xcm = Xcm(vec![instruction]); } : { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { assert_eq!( - executor.origin, - Some(MultiLocation { + executor.origin(), + &Some(MultiLocation { parents: 0, interior: who, }), @@ -176,22 +178,24 @@ benchmarks! { let instruction = Instruction::ClearOrigin; let xcm = Xcm(vec![instruction]); } : { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { - assert_eq!(executor.origin, None); + assert_eq!(executor.origin(), &None); } report_error { let mut executor = new_executor::(Default::default()); - executor.error = Some((0u32, XcmError::Unimplemented)); + executor.set_error(Some((0u32, XcmError::Unimplemented))); let query_id = Default::default(); - let dest = T::valid_destination().map_err(|_| BenchmarkError::Skip)?; - let max_response_weight = Default::default(); + let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?; + let max_weight = Default::default(); - let instruction = Instruction::ReportError { query_id, dest, max_response_weight }; + let instruction = Instruction::ReportError(QueryResponseInfo { + query_id, destination, max_weight + }); let xcm = Xcm(vec![instruction]); }: { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { // the execution succeeding is all we need to verify this xcm was successful } @@ -205,6 +209,11 @@ benchmarks! { ::AssetTrap::drop_assets( &origin, assets.clone().into(), + &XcmContext { + origin: Some(origin.clone()), + message_hash: [0; 32], + topic: None, + }, ); // Assets should be in the trap now. @@ -213,9 +222,9 @@ benchmarks! { let instruction = Instruction::ClaimAsset { assets: assets.clone(), ticket }; let xcm = Xcm(vec![instruction]); } :{ - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { - assert!(executor.holding.ensure_contains(&assets).is_ok()); + assert!(executor.holding().ensure_contains(&assets).is_ok()); } trap { @@ -225,14 +234,12 @@ benchmarks! { // In order to access result in the verification below, it needs to be defined here. let mut _result = Ok(()); } : { - _result = executor.execute(xcm); + _result = executor.bench_process(xcm); } verify { - match _result { - Err(error) if error.xcm_error == XcmError::Trap(10) => { - // This is the success condition - }, - _ => Err("xcm trap did not return the expected error")? - }; + assert!(matches!(_result, Err(ExecutorError { + xcm_error: XcmError::Trap(10), + .. + }))); } subscribe_version { @@ -244,7 +251,7 @@ benchmarks! { let instruction = Instruction::SubscribeVersion { query_id, max_response_weight }; let xcm = Xcm(vec![instruction]); } : { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { assert!(::SubscriptionService::is_subscribed(&origin)); } @@ -252,13 +259,18 @@ benchmarks! { unsubscribe_version { use xcm_executor::traits::VersionChangeNotifier; // First we need to subscribe to notifications. - let origin = T::transact_origin()?; + let (origin, _) = T::transact_origin_and_runtime_call()?; let query_id = Default::default(); let max_response_weight = Default::default(); ::SubscriptionService::start( &origin, query_id, - max_response_weight + max_response_weight, + &XcmContext { + origin: Some(origin.clone()), + message_hash: [0; 32], + topic: None, + }, ).map_err(|_| "Could not start subscription")?; assert!(::SubscriptionService::is_subscribed(&origin)); @@ -266,30 +278,330 @@ benchmarks! { let instruction = Instruction::UnsubscribeVersion; let xcm = Xcm(vec![instruction]); } : { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { assert!(!::SubscriptionService::is_subscribed(&origin)); } initiate_reserve_withdraw { - let holding = T::worst_case_holding(); + let holding = T::worst_case_holding(1); let assets_filter = MultiAssetFilter::Definite(holding.clone()); let reserve = T::valid_destination().map_err(|_| BenchmarkError::Skip)?; let mut executor = new_executor::(Default::default()); - executor.holding = holding.into(); + executor.set_holding(holding.into()); let instruction = Instruction::InitiateReserveWithdraw { assets: assets_filter, reserve, xcm: Xcm(vec![]) }; let xcm = Xcm(vec![instruction]); - }:{ - executor.execute(xcm)?; + }: { + executor.bench_process(xcm)?; } verify { // The execute completing successfully is as good as we can check. // TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426 } + burn_asset { + let holding = T::worst_case_holding(0); + let assets = holding.clone(); + + let mut executor = new_executor::(Default::default()); + executor.set_holding(holding.into()); + + let instruction = Instruction::BurnAsset(assets.into()); + let xcm = Xcm(vec![instruction]); + }: { + executor.bench_process(xcm)?; + } verify { + assert!(executor.holding().is_empty()); + } + + expect_asset { + let holding = T::worst_case_holding(0); + let assets = holding.clone(); + + let mut executor = new_executor::(Default::default()); + executor.set_holding(holding.into()); + + let instruction = Instruction::ExpectAsset(assets.into()); + let xcm = Xcm(vec![instruction]); + }: { + executor.bench_process(xcm)?; + } verify { + // `execute` completing successfully is as good as we can check. + } + + expect_origin { + let expected_origin = Parent.into(); + let mut executor = new_executor::(Default::default()); + + let instruction = Instruction::ExpectOrigin(Some(expected_origin)); + let xcm = Xcm(vec![instruction]); + let mut _result = Ok(()); + }: { + _result = executor.bench_process(xcm); + } verify { + assert!(matches!(_result, Err(ExecutorError { + xcm_error: XcmError::ExpectationFalse, + .. + }))); + } + + expect_error { + let mut executor = new_executor::(Default::default()); + executor.set_error(Some((3u32, XcmError::Overflow))); + + let instruction = Instruction::ExpectError(None); + let xcm = Xcm(vec![instruction]); + let mut _result = Ok(()); + }: { + _result = executor.bench_process(xcm); + } verify { + assert!(matches!(_result, Err(ExecutorError { + xcm_error: XcmError::ExpectationFalse, + .. + }))); + } + + query_pallet { + let query_id = Default::default(); + let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?; + let max_weight = Default::default(); + let mut executor = new_executor::(Default::default()); + + let instruction = Instruction::QueryPallet { + module_name: b"frame_system".to_vec(), + response_info: QueryResponseInfo { destination, query_id, max_weight }, + }; + let xcm = Xcm(vec![instruction]); + }: { + executor.bench_process(xcm)?; + } verify { + // TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426 + } + + expect_pallet { + let mut executor = new_executor::(Default::default()); + + let instruction = Instruction::ExpectPallet { + index: 0, + name: b"System".to_vec(), + module_name: b"frame_system".to_vec(), + crate_major: 4, + min_crate_minor: 0, + }; + let xcm = Xcm(vec![instruction]); + }: { + executor.bench_process(xcm)?; + } verify { + // the execution succeeding is all we need to verify this xcm was successful + } + + report_transact_status { + let query_id = Default::default(); + let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?; + let max_weight = Default::default(); + + let mut executor = new_executor::(Default::default()); + executor.set_transact_status(b"MyError".to_vec().into()); + + let instruction = Instruction::ReportTransactStatus(QueryResponseInfo { + query_id, + destination, + max_weight, + }); + let xcm = Xcm(vec![instruction]); + }: { + executor.bench_process(xcm)?; + } verify { + // TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426 + } + + clear_transact_status { + let mut executor = new_executor::(Default::default()); + executor.set_transact_status(MaybeErrorCode::Error(b"MyError".to_vec())); + + let instruction = Instruction::ClearTransactStatus; + let xcm = Xcm(vec![instruction]); + }: { + executor.bench_process(xcm)?; + } verify { + assert_eq!(executor.transact_status(), &MaybeErrorCode::Success); + } + + set_topic { + let mut executor = new_executor::(Default::default()); + + let instruction = Instruction::SetTopic([1; 32]); + let xcm = Xcm(vec![instruction]); + }: { + executor.bench_process(xcm)?; + } verify { + assert_eq!(executor.topic(), &Some([1; 32])); + } + + clear_topic { + let mut executor = new_executor::(Default::default()); + executor.set_topic(Some([2; 32])); + + let instruction = Instruction::ClearTopic; + let xcm = Xcm(vec![instruction]); + }: { + executor.bench_process(xcm)?; + } verify { + assert_eq!(executor.topic(), &None); + } + + exchange_asset { + let (give, want) = T::worst_case_asset_exchange().map_err(|_| BenchmarkError::Skip)?; + let assets = give.clone(); + + let mut executor = new_executor::(Default::default()); + executor.set_holding(give.into()); + let instruction = Instruction::ExchangeAsset { + give: assets.into(), + want: want.clone(), + maximal: true, + }; + let xcm = Xcm(vec![instruction]); + }: { + executor.bench_process(xcm)?; + } verify { + assert_eq!(executor.holding(), &want.into()); + } + + universal_origin { + let alias = T::universal_alias().map_err(|_| BenchmarkError::Skip)?; + + let mut executor = new_executor::(Here.into_location()); + + let instruction = Instruction::UniversalOrigin(alias.clone()); + let xcm = Xcm(vec![instruction]); + }: { + executor.bench_process(xcm)?; + } verify { + use frame_support::traits::Get; + let universal_location = ::UniversalLocation::get(); + assert_eq!(executor.origin(), &Some(X1(alias).relative_to(&universal_location))); + } + + set_fees_mode { + let mut executor = new_executor::(Default::default()); + executor.set_fees_mode(FeesMode { jit_withdraw: false }); + + let instruction = Instruction::SetFeesMode { jit_withdraw: true }; + let xcm = Xcm(vec![instruction]); + }: { + executor.bench_process(xcm)?; + } verify { + assert_eq!(executor.fees_mode(), &FeesMode { jit_withdraw: true }); + } + + lock_asset { + let (unlocker, owner, asset) = T::unlockable_asset()?; + + let mut executor = new_executor::(owner); + executor.set_holding(asset.clone().into()); + + let instruction = Instruction::LockAsset { asset, unlocker }; + let xcm = Xcm(vec![instruction]); + }: { + executor.bench_process(xcm)?; + } verify { + // TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426 + } + + unlock_asset { + use xcm_executor::traits::{AssetLock, Enact}; + + let (unlocker, owner, asset) = T::unlockable_asset()?; + + let mut executor = new_executor::(unlocker.clone()); + + // We first place the asset in lock first... + ::AssetLocker::prepare_lock( + unlocker, + asset.clone(), + owner.clone(), + ) + .map_err(|_| BenchmarkError::Skip)? + .enact() + .map_err(|_| BenchmarkError::Skip)?; + + // ... then unlock them with the UnlockAsset instruction. + let instruction = Instruction::UnlockAsset { asset, target: owner }; + let xcm = Xcm(vec![instruction]); + }: { + executor.bench_process(xcm)?; + } verify { + + } + + note_unlockable { + use xcm_executor::traits::{AssetLock, Enact}; + + let (unlocker, owner, asset) = T::unlockable_asset()?; + + let mut executor = new_executor::(unlocker.clone()); + + // We first place the asset in lock first... + ::AssetLocker::prepare_lock( + unlocker, + asset.clone(), + owner.clone(), + ) + .map_err(|_| BenchmarkError::Skip)? + .enact() + .map_err(|_| BenchmarkError::Skip)?; + + // ... then note them as unlockable with the NoteUnlockable instruction. + let instruction = Instruction::NoteUnlockable { asset, owner }; + let xcm = Xcm(vec![instruction]); + }: { + executor.bench_process(xcm)?; + } verify { + + } + + request_unlock { + use xcm_executor::traits::{AssetLock, Enact}; + + let (locker, owner, asset) = T::unlockable_asset()?; + + // We first place the asset in lock first... + ::AssetLocker::prepare_lock( + locker.clone(), + asset.clone(), + owner.clone(), + ) + .map_err(|_| BenchmarkError::Skip)? + .enact() + .map_err(|_| BenchmarkError::Skip)?; + + // ... then request for an unlock with the RequestUnlock instruction. + let mut executor = new_executor::(owner); + let instruction = Instruction::RequestUnlock { asset, locker }; + let xcm = Xcm(vec![instruction]); + }: { + executor.bench_process(xcm)?; + } verify { + // TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426 + } + + unpaid_execution { + let mut executor = new_executor::(Default::default()); + executor.set_origin(Some(Here.into())); + + let instruction = Instruction::>::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: Some(Here.into()), + }; + + let xcm = Xcm(vec![instruction]); + }: { + executor.bench_process(xcm)?; + } + impl_benchmark_test_suite!( Pallet, crate::generic::mock::new_test_ext(), crate::generic::mock::Test ); - } diff --git a/xcm/pallet-xcm-benchmarks/src/generic/mock.rs b/xcm/pallet-xcm-benchmarks/src/generic/mock.rs index 1aeec6c317fe..79f9f28e32f5 100644 --- a/xcm/pallet-xcm-benchmarks/src/generic/mock.rs +++ b/xcm/pallet-xcm-benchmarks/src/generic/mock.rs @@ -30,7 +30,10 @@ use sp_runtime::{ BuildStorage, }; use xcm_builder::{ - test_utils::{Assets, TestAssetTrap, TestSubscriptionService}, + test_utils::{ + Assets, TestAssetExchanger, TestAssetLocker, TestAssetTrap, TestSubscriptionService, + TestUniversalAliases, + }, AllowUnpaidExecutionFrom, }; use xcm_executor::traits::ConvertOrigin; @@ -52,9 +55,7 @@ frame_support::construct_runtime!( parameter_types! { pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max( - Weight::from_ref_time(1024).set_proof_size(u64::MAX), - ); + frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, u64::MAX)); } impl frame_system::Config for Test { @@ -87,17 +88,22 @@ impl frame_system::Config for Test { /// The benchmarks in this pallet should never need an asset transactor to begin with. pub struct NoAssetTransactor; impl xcm_executor::traits::TransactAsset for NoAssetTransactor { - fn deposit_asset(_: &MultiAsset, _: &MultiLocation) -> Result<(), XcmError> { + fn deposit_asset(_: &MultiAsset, _: &MultiLocation, _: &XcmContext) -> Result<(), XcmError> { unreachable!(); } - fn withdraw_asset(_: &MultiAsset, _: &MultiLocation) -> Result { + fn withdraw_asset( + _: &MultiAsset, + _: &MultiLocation, + _: Option<&XcmContext>, + ) -> Result { unreachable!(); } } parameter_types! { pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } pub struct XcmConfig; @@ -108,14 +114,24 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = AlwaysSignedByDefault; type IsReserve = AllAssetLocationsPass; type IsTeleporter = (); - type LocationInverter = xcm_builder::LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = AllowUnpaidExecutionFrom; type Weigher = xcm_builder::FixedWeightBounds; type Trader = xcm_builder::FixedRateOfFungible; type ResponseHandler = DevNull; type AssetTrap = TestAssetTrap; + type AssetLocker = TestAssetLocker; + type AssetExchanger = TestAssetExchanger; type AssetClaims = TestAssetTrap; type SubscriptionService = TestSubscriptionService; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + // No bridges yet... + type MessageExporter = (); + type UniversalAliases = TestUniversalAliases; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; } impl crate::Config for Test { @@ -123,12 +139,15 @@ impl crate::Config for Test { type AccountIdConverter = AccountIdConverter; fn valid_destination() -> Result { let valid_destination: MultiLocation = - Junction::AccountId32 { network: NetworkId::Any, id: [0u8; 32] }.into(); + Junction::AccountId32 { network: None, id: [0u8; 32] }.into(); Ok(valid_destination) } - fn worst_case_holding() -> MultiAssets { - crate::mock_worst_case_holding() + fn worst_case_holding(depositable_count: u32) -> MultiAssets { + crate::mock_worst_case_holding( + depositable_count, + ::MaxAssetsIntoHolding::get(), + ) } } @@ -140,10 +159,19 @@ impl generic::Config for Test { (0, Response::Assets(assets)) } - fn transact_origin() -> Result { + fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { Ok(Default::default()) } + fn universal_alias() -> Result { + Ok(GlobalConsensus(ByGenesis([0; 32]))) + } + + fn transact_origin_and_runtime_call( + ) -> Result<(MultiLocation, ::RuntimeCall), BenchmarkError> { + Ok((Default::default(), frame_system::Call::remark_with_event { remark: vec![] }.into())) + } + fn subscribe_origin() -> Result { Ok(Default::default()) } @@ -153,6 +181,11 @@ impl generic::Config for Test { let ticket = MultiLocation { parents: 0, interior: X1(GeneralIndex(0)) }; Ok((Default::default(), ticket, assets)) } + + fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + let assets: MultiAsset = (Concrete(Here.into()), 100).into(); + Ok((Default::default(), Default::default(), assets)) + } } pub fn new_test_ext() -> sp_io::TestExternalities { diff --git a/xcm/pallet-xcm-benchmarks/src/generic/mod.rs b/xcm/pallet-xcm-benchmarks/src/generic/mod.rs index de347a537be2..d60081c19182 100644 --- a/xcm/pallet-xcm-benchmarks/src/generic/mod.rs +++ b/xcm/pallet-xcm-benchmarks/src/generic/mod.rs @@ -12,7 +12,7 @@ pub mod pallet { dispatch::{Dispatchable, GetDispatchInfo}, pallet_prelude::Encode, }; - use xcm::latest::{MultiAssets, MultiLocation, Response}; + use xcm::latest::{Junction, MultiAsset, MultiAssets, MultiLocation, Response}; #[pallet::config] pub trait Config: frame_system::Config + crate::Config { @@ -24,18 +24,37 @@ pub mod pallet { /// The response which causes the most runtime weight. fn worst_case_response() -> (u64, Response); - /// The `MultiLocation` used for successful transaction XCMs. + /// The pair of asset collections which causes the most runtime weight if demanded to be + /// exchanged. /// - /// If set to `None`, benchmarks which rely on a `transact_origin` will be skipped. - fn transact_origin() -> Result; + /// The first element in the returned tuple represents the assets that are being exchanged + /// from, whereas the second element represents the assets that are being exchanged to. + /// + /// If set to `Err`, benchmarks which rely on an `exchange_asset` will be skipped. + fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError>; + + /// A `Junction` that is one of the `UniversalAliases` configured by the XCM executor. + /// + /// If set to `Err`, benchmarks which rely on a universal alias will be skipped. + fn universal_alias() -> Result; + + /// The `MultiLocation` and `RuntimeCall` used for successful transaction XCMs. + /// + /// If set to `Err`, benchmarks which rely on a `transact_origin_and_runtime_call` will be + /// skipped. + fn transact_origin_and_runtime_call( + ) -> Result<(MultiLocation, >::RuntimeCall), BenchmarkError>; /// A valid `MultiLocation` we can successfully subscribe to. /// - /// If set to `None`, benchmarks which rely on a `subscribe_origin` will be skipped. + /// If set to `Err`, benchmarks which rely on a `subscribe_origin` will be skipped. fn subscribe_origin() -> Result; /// Return an origin, ticket, and assets that can be trapped and claimed. fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError>; + + /// Return an unlocker, owner and assets that can be locked and unlocked. + fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError>; } #[pallet::pallet] diff --git a/xcm/pallet-xcm-benchmarks/src/lib.rs b/xcm/pallet-xcm-benchmarks/src/lib.rs index 352f7376ef46..c2bb093b1d1a 100644 --- a/xcm/pallet-xcm-benchmarks/src/lib.rs +++ b/xcm/pallet-xcm-benchmarks/src/lib.rs @@ -22,7 +22,7 @@ use codec::Encode; use frame_benchmarking::{account, BenchmarkError}; use sp_std::prelude::*; use xcm::latest::prelude::*; -use xcm_executor::traits::Convert; +use xcm_executor::{traits::Convert, Config as XcmConfig}; pub mod fungible; pub mod generic; @@ -36,7 +36,7 @@ pub trait Config: frame_system::Config { /// /// These might affect the execution of XCM messages, such as defining how the /// `TransactAsset` is implemented. - type XcmConfig: xcm_executor::Config; + type XcmConfig: XcmConfig; /// A converter between a multi-location to a sovereign account. type AccountIdConverter: Convert; @@ -46,7 +46,7 @@ pub trait Config: frame_system::Config { fn valid_destination() -> Result; /// Worst case scenario for a holding account in this runtime. - fn worst_case_holding() -> MultiAssets; + fn worst_case_holding(depositable_count: u32) -> MultiAssets; } const SEED: u32 = 0; @@ -56,15 +56,15 @@ pub type ExecutorOf = xcm_executor::XcmExecutor<::XcmConfig>; /// The overarching call type. pub type OverArchingCallOf = ::RuntimeCall; /// The asset transactor of our executor -pub type AssetTransactorOf = <::XcmConfig as xcm_executor::Config>::AssetTransactor; +pub type AssetTransactorOf = <::XcmConfig as XcmConfig>::AssetTransactor; /// The call type of executor's config. Should eventually resolve to the same overarching call type. -pub type XcmCallOf = <::XcmConfig as xcm_executor::Config>::RuntimeCall; +pub type XcmCallOf = <::XcmConfig as XcmConfig>::RuntimeCall; -pub fn mock_worst_case_holding() -> MultiAssets { - const HOLDING_FUNGIBLES: u32 = 99; - const HOLDING_NON_FUNGIBLES: u32 = 99; +pub fn mock_worst_case_holding(depositable_count: u32, max_assets: u32) -> MultiAssets { let fungibles_amount: u128 = 100; - (0..HOLDING_FUNGIBLES) + let holding_fungibles = max_assets / 2 - depositable_count; + let holding_non_fungibles = holding_fungibles; + (0..holding_fungibles) .map(|i| { MultiAsset { id: Concrete(GeneralIndex(i as u128).into()), @@ -73,7 +73,7 @@ pub fn mock_worst_case_holding() -> MultiAssets { .into() }) .chain(core::iter::once(MultiAsset { id: Concrete(Here.into()), fun: Fungible(u128::MAX) })) - .chain((0..HOLDING_NON_FUNGIBLES).map(|i| MultiAsset { + .chain((0..holding_non_fungibles).map(|i| MultiAsset { id: Concrete(GeneralIndex(i as u128).into()), fun: NonFungible(asset_instance_from(i)), })) @@ -89,7 +89,7 @@ pub fn asset_instance_from(x: u32) -> AssetInstance { } pub fn new_executor(origin: MultiLocation) -> ExecutorOf { - ExecutorOf::::new(origin) + ExecutorOf::::new(origin, [0; 32]) } /// Build a multi-location from an account id. @@ -99,7 +99,7 @@ fn account_id_junction(index: u32) -> Junction { encoded.resize(32, 0u8); let mut id = [0u8; 32]; id.copy_from_slice(&encoded); - Junction::AccountId32 { network: NetworkId::Any, id } + Junction::AccountId32 { network: None, id } } pub fn account_and_location(index: u32) -> (T::AccountId, MultiLocation) { diff --git a/xcm/pallet-xcm-benchmarks/src/mock.rs b/xcm/pallet-xcm-benchmarks/src/mock.rs index 9503f0756381..905a9af36fc4 100644 --- a/xcm/pallet-xcm-benchmarks/src/mock.rs +++ b/xcm/pallet-xcm-benchmarks/src/mock.rs @@ -15,24 +15,34 @@ // along with Polkadot. If not, see . use crate::*; -use frame_support::parameter_types; -use xcm::latest::Weight as XCMWeight; -use xcm_executor::traits::FilterAssetLocation; +use frame_support::{parameter_types, traits::ContainsPair}; +use xcm::latest::Weight; // An xcm sender/receiver akin to > /dev/null pub struct DevNull; impl xcm::opaque::latest::SendXcm for DevNull { - fn send_xcm(_: impl Into, _: Xcm<()>) -> SendResult { - Ok(()) + type Ticket = (); + fn validate(_: &mut Option, _: &mut Option>) -> SendResult<()> { + Ok(((), MultiAssets::new())) + } + fn deliver(_: ()) -> Result { + Ok([0; 32]) } } impl xcm_executor::traits::OnResponse for DevNull { - fn expecting_response(_: &MultiLocation, _: u64) -> bool { + fn expecting_response(_: &MultiLocation, _: u64, _: Option<&MultiLocation>) -> bool { false } - fn on_response(_: &MultiLocation, _: u64, _: Response, _: XCMWeight) -> XCMWeight { - 0 + fn on_response( + _: &MultiLocation, + _: u64, + _: Option<&MultiLocation>, + _: Response, + _: Weight, + _: &XcmContext, + ) -> Weight { + Weight::zero() } } @@ -52,14 +62,14 @@ impl xcm_executor::traits::Convert for AccountIdConverter { } parameter_types! { - pub Ancestry: MultiLocation = Junction::Parachain(101).into(); - pub UnitWeightCost: u64 = 10; - pub WeightPrice: (AssetId, u128) = (Concrete(Here.into()), 1_000_000); + pub UniversalLocation: InteriorMultiLocation = Junction::Parachain(101).into(); + pub UnitWeightCost: Weight = Weight::from_parts(10, 10); + pub WeightPrice: (AssetId, u128, u128) = (Concrete(Here.into()), 1_000_000, 1024); } pub struct AllAssetLocationsPass; -impl FilterAssetLocation for AllAssetLocationsPass { - fn filter_asset_location(_: &MultiAsset, _: &MultiLocation) -> bool { +impl ContainsPair for AllAssetLocationsPass { + fn contains(_: &MultiAsset, _: &MultiLocation) -> bool { true } } diff --git a/xcm/pallet-xcm/Cargo.toml b/xcm/pallet-xcm/Cargo.toml index 4eb1c558b8ce..1b9de5fd5d44 100644 --- a/xcm/pallet-xcm/Cargo.toml +++ b/xcm/pallet-xcm/Cargo.toml @@ -11,11 +11,13 @@ scale-info = { version = "2.1.2", default-features = false, features = ["derive" serde = { version = "1.0.137", optional = true, features = ["derive"] } log = { version = "0.4.17", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } -sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } -sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } xcm = { path = "..", default-features = false } xcm-executor = { path = "../xcm-executor", default-features = false } @@ -23,9 +25,8 @@ xcm-executor = { path = "../xcm-executor", default-features = false } [dev-dependencies] pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" } polkadot-runtime-parachains = { path = "../../runtime/parachains" } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } -xcm-builder = { path = "../xcm-builder" } polkadot-parachain = { path = "../../parachain" } +xcm-builder = { path = "../xcm-builder" } [features] default = ["std"] @@ -35,13 +36,16 @@ std = [ "serde", "sp-std/std", "sp-core/std", + "sp-io/std", "sp-runtime/std", + "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "xcm/std", "xcm-executor/std", ] runtime-benchmarks = [ - "frame-system/runtime-benchmarks" + "frame-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/xcm/pallet-xcm/src/benchmarking.rs b/xcm/pallet-xcm/src/benchmarking.rs new file mode 100644 index 000000000000..fff4b8b2ead4 --- /dev/null +++ b/xcm/pallet-xcm/src/benchmarking.rs @@ -0,0 +1,192 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use super::*; +use frame_benchmarking::{benchmarks, BenchmarkError, BenchmarkResult}; +use frame_support::weights::Weight; +use frame_system::RawOrigin; +use sp_core::{bounded::WeakBoundedVec, ConstU32}; +use sp_std::prelude::*; +use xcm::{latest::prelude::*, v2}; + +type RuntimeOrigin = ::RuntimeOrigin; + +benchmarks! { + send { + let send_origin = T::SendXcmOrigin::successful_origin(); + if T::SendXcmOrigin::try_origin(send_origin.clone()).is_err() { + return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) + } + let msg = Xcm(vec![ClearOrigin]); + let versioned_dest: VersionedMultiLocation = T::ReachableDest::get().ok_or( + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), + )? + .into(); + let versioned_msg = VersionedXcm::from(msg); + }: _>(send_origin, Box::new(versioned_dest), Box::new(versioned_msg)) + + teleport_assets { + let asset: MultiAsset = (Here, 10).into(); + let send_origin = T::ExecuteXcmOrigin::successful_origin(); + let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone()) + .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; + if !T::XcmTeleportFilter::contains(&(origin_location, vec![asset.clone()])) { + return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) + } + + let recipient = [0u8; 32]; + let versioned_dest: VersionedMultiLocation = T::ReachableDest::get().ok_or( + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), + )? + .into(); + let versioned_beneficiary: VersionedMultiLocation = + AccountId32 { network: None, id: recipient.into() }.into(); + let versioned_assets: VersionedMultiAssets = asset.into(); + }: _>(send_origin, Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0) + + reserve_transfer_assets { + let asset: MultiAsset = (Here, 10).into(); + let send_origin = T::ExecuteXcmOrigin::successful_origin(); + let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone()) + .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; + if !T::XcmReserveTransferFilter::contains(&(origin_location, vec![asset.clone()])) { + return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) + } + + let recipient = [0u8; 32]; + let versioned_dest: VersionedMultiLocation = T::ReachableDest::get().ok_or( + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), + )? + .into(); + let versioned_beneficiary: VersionedMultiLocation = + AccountId32 { network: None, id: recipient.into() }.into(); + let versioned_assets: VersionedMultiAssets = asset.into(); + }: _>(send_origin, Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0) + + execute { + let execute_origin = T::ExecuteXcmOrigin::successful_origin(); + let origin_location = T::ExecuteXcmOrigin::try_origin(execute_origin.clone()) + .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; + let msg = Xcm(vec![ClearOrigin]); + if !T::XcmExecuteFilter::contains(&(origin_location, msg.clone())) { + return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) + } + let versioned_msg = VersionedXcm::from(msg); + }: _>(execute_origin, Box::new(versioned_msg), Weight::zero()) + + force_xcm_version { + let loc = T::ReachableDest::get().ok_or( + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), + )?; + let xcm_version = 2; + }: _(RawOrigin::Root, Box::new(loc), xcm_version) + + force_default_xcm_version {}: _(RawOrigin::Root, Some(2)) + + force_subscribe_version_notify { + let versioned_loc: VersionedMultiLocation = T::ReachableDest::get().ok_or( + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), + )? + .into(); + }: _(RawOrigin::Root, Box::new(versioned_loc)) + + force_unsubscribe_version_notify { + let loc = T::ReachableDest::get().ok_or( + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), + )?; + let versioned_loc: VersionedMultiLocation = loc.into(); + let _ = Pallet::::request_version_notify(loc); + }: _(RawOrigin::Root, Box::new(versioned_loc)) + + migrate_supported_version { + let old_version = XCM_VERSION - 1; + let loc = VersionedMultiLocation::from(MultiLocation::from(Parent)); + SupportedVersion::::insert(old_version, loc, old_version); + }: { + Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateSupportedVersion, Weight::zero()); + } + + migrate_version_notifiers { + let old_version = XCM_VERSION - 1; + let loc = VersionedMultiLocation::from(MultiLocation::from(Parent)); + VersionNotifiers::::insert(old_version, loc, 0); + }: { + Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateVersionNotifiers, Weight::zero()); + } + + already_notified_target { + let loc = T::ReachableDest::get().ok_or( + BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads(1))), + )?; + let loc = VersionedMultiLocation::from(loc); + let current_version = T::AdvertisedXcmVersion::get(); + VersionNotifyTargets::::insert(current_version, loc, (0, Weight::zero(), current_version)); + }: { + Pallet::::check_xcm_version_change(VersionMigrationStage::NotifyCurrentTargets(None), Weight::zero()); + } + + notify_current_targets { + let loc = T::ReachableDest::get().ok_or( + BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3))), + )?; + let loc = VersionedMultiLocation::from(loc); + let current_version = T::AdvertisedXcmVersion::get(); + let old_version = current_version - 1; + VersionNotifyTargets::::insert(current_version, loc, (0, Weight::zero(), old_version)); + }: { + Pallet::::check_xcm_version_change(VersionMigrationStage::NotifyCurrentTargets(None), Weight::zero()); + } + + notify_target_migration_fail { + let bad_loc: v2::MultiLocation = v2::Junction::Plurality { + id: v2::BodyId::Named(WeakBoundedVec::>::try_from(vec![0; 32]) + .expect("vec has a length of 32 bits; qed")), + part: v2::BodyPart::Voice, + } + .into(); + let bad_loc = VersionedMultiLocation::from(bad_loc); + let current_version = T::AdvertisedXcmVersion::get(); + VersionNotifyTargets::::insert(current_version, bad_loc, (0, Weight::zero(), current_version)); + }: { + Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero()); + } + + migrate_version_notify_targets { + let current_version = T::AdvertisedXcmVersion::get(); + let old_version = current_version - 1; + let loc = VersionedMultiLocation::from(MultiLocation::from(Parent)); + VersionNotifyTargets::::insert(old_version, loc, (0, Weight::zero(), current_version)); + }: { + Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero()); + } + + migrate_and_notify_old_targets { + let loc = T::ReachableDest::get().ok_or( + BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3))), + )?; + let loc = VersionedMultiLocation::from(loc); + let old_version = T::AdvertisedXcmVersion::get() - 1; + VersionNotifyTargets::::insert(old_version, loc, (0, Weight::zero(), old_version)); + }: { + Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero()); + } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::new_test_ext_with_balances(Vec::new()), + crate::mock::Test + ); +} diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 3502c79b5d25..323180d2f90a 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -18,43 +18,137 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; #[cfg(test)] mod mock; #[cfg(test)] mod tests; +pub mod migration; + use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; -use frame_support::traits::{Contains, EnsureOrigin, Get, OriginTrait}; +use frame_support::traits::{ + Contains, ContainsPair, Currency, Defensive, EnsureOrigin, Get, LockableCurrency, OriginTrait, +}; use scale_info::TypeInfo; use sp_runtime::{ - traits::{BadOrigin, Saturating}, + traits::{ + AccountIdConversion, BadOrigin, BlakeTwo256, BlockNumberProvider, Hash, Saturating, Zero, + }, RuntimeDebug, }; use sp_std::{boxed::Box, marker::PhantomData, prelude::*, result::Result, vec}; -use xcm::{latest::Weight as XcmWeight, prelude::*}; -use xcm_executor::traits::ConvertOrigin; - -use frame_support::PalletId; +use xcm::{latest::QueryResponseInfo, prelude::*}; +use xcm_executor::traits::{Convert, ConvertOrigin}; + +use frame_support::{ + dispatch::{Dispatchable, GetDispatchInfo}, + pallet_prelude::*, + traits::WithdrawReasons, + PalletId, +}; +use frame_system::pallet_prelude::*; pub use pallet::*; +use xcm_executor::{ + traits::{ + ClaimAssets, DropAssets, MatchesFungible, OnResponse, VersionChangeNotifier, WeightBounds, + }, + Assets, +}; + +pub trait WeightInfo { + fn send() -> Weight; + fn teleport_assets() -> Weight; + fn reserve_transfer_assets() -> Weight; + fn execute() -> Weight; + fn force_xcm_version() -> Weight; + fn force_default_xcm_version() -> Weight; + fn force_subscribe_version_notify() -> Weight; + fn force_unsubscribe_version_notify() -> Weight; + fn migrate_supported_version() -> Weight; + fn migrate_version_notifiers() -> Weight; + fn already_notified_target() -> Weight; + fn notify_current_targets() -> Weight; + fn notify_target_migration_fail() -> Weight; + fn migrate_version_notify_targets() -> Weight; + fn migrate_and_notify_old_targets() -> Weight; +} + +/// fallback implementation +pub struct TestWeightInfo; +impl WeightInfo for TestWeightInfo { + fn send() -> Weight { + Weight::from_ref_time(100_000_000) + } + + fn teleport_assets() -> Weight { + Weight::from_ref_time(100_000_000) + } + + fn reserve_transfer_assets() -> Weight { + Weight::from_ref_time(100_000_000) + } + + fn execute() -> Weight { + Weight::from_ref_time(100_000_000) + } + + fn force_xcm_version() -> Weight { + Weight::from_ref_time(100_000_000) + } + + fn force_default_xcm_version() -> Weight { + Weight::from_ref_time(100_000_000) + } + + fn force_subscribe_version_notify() -> Weight { + Weight::from_ref_time(100_000_000) + } + + fn force_unsubscribe_version_notify() -> Weight { + Weight::from_ref_time(100_000_000) + } + + fn migrate_supported_version() -> Weight { + Weight::from_ref_time(100_000_000) + } + + fn migrate_version_notifiers() -> Weight { + Weight::from_ref_time(100_000_000) + } + + fn already_notified_target() -> Weight { + Weight::from_ref_time(100_000_000) + } + + fn notify_current_targets() -> Weight { + Weight::from_ref_time(100_000_000) + } + + fn notify_target_migration_fail() -> Weight { + Weight::from_ref_time(100_000_000) + } + + fn migrate_version_notify_targets() -> Weight { + Weight::from_ref_time(100_000_000) + } + + fn migrate_and_notify_old_targets() -> Weight { + Weight::from_ref_time(100_000_000) + } +} #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, - pallet_prelude::*, parameter_types, }; - use frame_system::{pallet_prelude::*, Config as SysConfig}; + use frame_system::Config as SysConfig; use sp_core::H256; - use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, BlockNumberProvider, Hash}; - use xcm_executor::{ - traits::{ - ClaimAssets, DropAssets, InvertLocation, OnResponse, VersionChangeNotifier, - WeightBounds, - }, - Assets, - }; + use xcm_executor::traits::{MatchesFungible, WeightBounds}; parameter_types! { /// An implementation of `Get` which just returns the latest XCM version which we can @@ -64,15 +158,26 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(migration::STORAGE_VERSION)] #[pallet::without_storage_info] pub struct Pallet(_); + pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + #[pallet::config] /// The module configuration trait. pub trait Config: frame_system::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// A lockable currency. + // TODO: We should really use a trait which can handle multiple currencies. + type Currency: LockableCurrency; + + /// The `MultiAsset` matcher for `Currency`. + type CurrencyMatcher: MatchesFungible>; + /// Required origin for sending XCM messages. If successful, it resolves to `MultiLocation` /// which exists as an interior location within this chain's XCM context. type SendXcmOrigin: EnsureOrigin< @@ -106,13 +211,13 @@ pub mod pallet { /// Means of measuring the weight consumed by an XCM message locally. type Weigher: WeightBounds<::RuntimeCall>; - /// Means of inverting a location. - type LocationInverter: InvertLocation; + /// This chain's Universal Location. + type UniversalLocation: Get; - /// The outer `Origin` type. + /// The runtime `Origin` type. type RuntimeOrigin: From + From<::RuntimeOrigin>; - /// The outer `Call` type. + /// The runtime `Call` type. type RuntimeCall: Parameter + GetDispatchInfo + IsType<::RuntimeCall> @@ -126,10 +231,26 @@ pub mod pallet { /// The latest supported version that we advertise. Generally just set it to /// `pallet_xcm::CurrentXcmVersion`. type AdvertisedXcmVersion: Get; - } - /// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic. - const MAX_ASSETS_FOR_TRANSFER: usize = 2; + /// The assets which we consider a given origin is trusted if they claim to have placed a + /// lock. + type TrustedLockers: ContainsPair; + + /// How to get an `AccountId` value from a `MultiLocation`, useful for handling asset locks. + type SovereignAccountOf: Convert; + + /// The maximum number of local XCM locks that a single account may have. + type MaxLockers: Get; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// A `MultiLocation` that can be reached via `XcmRouter`. Used only in benchmarks. + /// + /// If `None`, the benchmarks that depend on a reachable destination will be skipped. + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest: Get>; + } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -201,8 +322,10 @@ pub mod pallet { AssetsTrapped(H256, MultiLocation, VersionedMultiAssets), /// An XCM version change notification message has been attempted to be sent. /// - /// \[ destination, result \] - VersionChangeNotified(MultiLocation, XcmVersion), + /// The cost of sending it (borne by the chain) is included. + /// + /// \[ destination, result, cost \] + VersionChangeNotified(MultiLocation, XcmVersion, MultiAssets), /// The supported version of a location has been changed. This might be through an /// automatic notification or a manual intervention. /// @@ -218,6 +341,39 @@ pub mod pallet { /// /// \[ location, query ID \] NotifyTargetMigrationFail(VersionedMultiLocation, QueryId), + /// Expected query response has been received but the expected querier location placed in + /// storage by this runtime previously cannot be decoded. The query remains registered. + /// + /// This is unexpected (since a location placed in storage in a previously executing + /// runtime should be readable prior to query timeout) and dangerous since the possibly + /// valid response will be dropped. Manual governance intervention is probably going to be + /// needed. + /// + /// \[ origin location, id \] + InvalidQuerierVersion(MultiLocation, QueryId), + /// Expected query response has been received but the querier location of the response does + /// not match the expected. The query remains registered for a later, valid, response to + /// be received and acted upon. + /// + /// \[ origin location, id, expected querier, maybe actual querier \] + InvalidQuerier(MultiLocation, QueryId, MultiLocation, Option), + /// A remote has requested XCM version change notification from us and we have honored it. + /// A version information message is sent to them and its cost is included. + /// + /// \[ destination location, cost \] + VersionNotifyStarted(MultiLocation, MultiAssets), + /// We have requested that a remote chain sends us XCM version change notifications. + /// + /// \[ destination location, cost \] + VersionNotifyRequested(MultiLocation, MultiAssets), + /// We have requested that a remote chain stops sending us XCM version change notifications. + /// + /// \[ destination location, cost \] + VersionNotifyUnrequested(MultiLocation, MultiAssets), + /// Fees were paid from a location for an operation (often for using `SendXcm`). + /// + /// \[ paying location, fees \] + FeesPaid(MultiLocation, MultiAssets), /// Some assets have been claimed from an asset trap /// /// \[ hash, origin, assets \] @@ -269,6 +425,30 @@ pub mod pallet { NoSubscription, /// The location is invalid since it already has a subscription from us. AlreadySubscribed, + /// Invalid asset for the operation. + InvalidAsset, + /// The owner does not own (all) of the asset that they wish to do the operation on. + LowBalance, + /// The asset owner has too many locks on the asset. + TooManyLocks, + /// The given account is not an identifiable sovereign account for any location. + AccountNotSovereign, + /// The operation required fees to be paid which the initiator could not meet. + FeesNotMet, + /// A remote lock with the corresponding data could not be found. + LockNotFound, + /// The unlock operation cannot succeed because there are still users of the lock. + InUse, + } + + impl From for Error { + fn from(e: SendError) -> Self { + match e { + SendError::Fees => Error::::FeesNotMet, + SendError::NotApplicable => Error::::Unreachable, + _ => Error::::SendFailure, + } + } } /// The status of a query. @@ -276,7 +456,12 @@ pub mod pallet { pub enum QueryStatus { /// The query was sent but no response has yet been received. Pending { + /// The `QueryResponse` XCM must have this origin to be considered a reply for this + /// query. responder: VersionedMultiLocation, + /// The `QueryResponse` XCM must have this value as the `querier` field to be + /// considered a reply for this query. If `None` then the querier is ignored. + maybe_match_querier: Option, maybe_notify: Option<(u8, u8)>, timeout: BlockNumber, }, @@ -368,7 +553,7 @@ pub mod pallet { XcmVersion, Blake2_128Concat, VersionedMultiLocation, - (QueryId, u64, XcmVersion), + (QueryId, Weight, XcmVersion), OptionQuery, >; @@ -394,6 +579,37 @@ pub mod pallet { pub(super) type CurrentMigration = StorageValue<_, VersionMigrationStage, OptionQuery>; + #[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo, MaxEncodedLen)] + pub struct RemoteLockedFungibleRecord { + pub amount: u128, + pub owner: VersionedMultiLocation, + pub locker: VersionedMultiLocation, + pub users: u32, + } + + /// Fungible assets which we know are locked on a remote chain. + #[pallet::storage] + pub(super) type RemoteLockedFungibles = StorageNMap< + _, + ( + NMapKey, + NMapKey, + NMapKey, + ), + RemoteLockedFungibleRecord, + OptionQuery, + >; + + /// Fungible assets which we know are locked on this chain. + #[pallet::storage] + pub(super) type LockedFungibles = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + BoundedVec<(BalanceOf, VersionedMultiLocation), T::MaxLockers>, + OptionQuery, + >; + #[pallet::genesis_config] pub struct GenesisConfig { /// The default version to encode outgoing XCM messages with. @@ -456,10 +672,90 @@ pub mod pallet { } } + pub mod migrations { + use super::*; + use frame_support::traits::{PalletInfoAccess, StorageVersion}; + + #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + enum QueryStatusV0 { + Pending { + responder: VersionedMultiLocation, + maybe_notify: Option<(u8, u8)>, + timeout: BlockNumber, + }, + VersionNotifier { + origin: VersionedMultiLocation, + is_active: bool, + }, + Ready { + response: VersionedResponse, + at: BlockNumber, + }, + } + impl From> for QueryStatus { + fn from(old: QueryStatusV0) -> Self { + use QueryStatusV0::*; + match old { + Pending { responder, maybe_notify, timeout } => QueryStatus::Pending { + responder, + maybe_notify, + timeout, + maybe_match_querier: Some(MultiLocation::here().into()), + }, + VersionNotifier { origin, is_active } => + QueryStatus::VersionNotifier { origin, is_active }, + Ready { response, at } => QueryStatus::Ready { response, at }, + } + } + } + + pub fn migrate_to_v1( + ) -> frame_support::weights::Weight { + let on_chain_storage_version =

::on_chain_storage_version(); + log::info!( + target: "runtime::xcm", + "Running migration storage v1 for xcm with storage version {:?}", + on_chain_storage_version, + ); + + if on_chain_storage_version < 1 { + let mut count = 0; + Queries::::translate::, _>(|_key, value| { + count += 1; + Some(value.into()) + }); + StorageVersion::new(1).put::

(); + log::info!( + target: "runtime::xcm", + "Running migration storage v1 for xcm with storage version {:?} was complete", + on_chain_storage_version, + ); + // calculate and return migration weights + T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1) + } else { + log::warn!( + target: "runtime::xcm", + "Attempted to apply migration to v1 but failed because storage version is {:?}", + on_chain_storage_version, + ); + T::DbWeight::get().reads(1) + } + } + } + #[pallet::call] impl Pallet { #[pallet::call_index(0)] - #[pallet::weight(100_000_000)] + #[pallet::weight({ + let maybe_msg: Result, ()> = (*message.clone()).try_into(); + match maybe_msg { + Ok(msg) => { + T::Weigher::weight(&mut msg.into()) + .map_or(Weight::MAX, |w| T::WeightInfo::send().saturating_add(w)) + } + _ => Weight::MAX, + } + })] pub fn send( origin: OriginFor, dest: Box, @@ -467,14 +763,11 @@ pub mod pallet { ) -> DispatchResult { let origin_location = T::SendXcmOrigin::ensure_origin(origin)?; let interior: Junctions = - origin_location.clone().try_into().map_err(|_| Error::::InvalidOrigin)?; + origin_location.try_into().map_err(|_| Error::::InvalidOrigin)?; let dest = MultiLocation::try_from(*dest).map_err(|()| Error::::BadVersion)?; let message: Xcm<()> = (*message).try_into().map_err(|()| Error::::BadVersion)?; - Self::send_xcm(interior, dest.clone(), message.clone()).map_err(|e| match e { - SendError::CannotReachDestination(..) => Error::::Unreachable, - _ => Error::::SendFailure, - })?; + Self::send_xcm(interior, dest, message.clone()).map_err(Error::::from)?; Self::deposit_event(Event::Sent(origin_location, dest, message)); Ok(()) } @@ -501,12 +794,17 @@ pub mod pallet { match (maybe_assets, maybe_dest) { (Ok(assets), Ok(dest)) => { use sp_std::vec; + let count = assets.len() as u32; let mut message = Xcm(vec![ WithdrawAsset(assets), - InitiateTeleport { assets: Wild(All), dest, xcm: Xcm(vec![]) }, + InitiateTeleport { + assets: Wild(AllCounted(count)), + dest, + xcm: Xcm(vec![]), + }, ]); - T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| Weight::from_ref_time(100_000_000.saturating_add(w))) - }, + T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::teleport_assets().saturating_add(w)) + } _ => Weight::MAX, } })] @@ -538,14 +836,16 @@ pub mod pallet { /// fees. #[pallet::call_index(2)] #[pallet::weight({ - match ((*assets.clone()).try_into(), (*dest.clone()).try_into()) { + let maybe_assets: Result = (*assets.clone()).try_into(); + let maybe_dest: Result = (*dest.clone()).try_into(); + match (maybe_assets, maybe_dest) { (Ok(assets), Ok(dest)) => { use sp_std::vec; let mut message = Xcm(vec![ TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) } ]); - T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| Weight::from_ref_time(100_000_000.saturating_add(w))) - }, + T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::reserve_transfer_assets().saturating_add(w)) + } _ => Weight::MAX, } })] @@ -578,13 +878,14 @@ pub mod pallet { /// NOTE: A successful return to this does *not* imply that the `msg` was executed successfully /// to completion; only that *some* of it was executed. #[pallet::call_index(3)] - #[pallet::weight(Weight::from_ref_time(max_weight.saturating_add(100_000_000u64)))] + #[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))] pub fn execute( origin: OriginFor, message: Box::RuntimeCall>>, - max_weight: XcmWeight, + max_weight: Weight, ) -> DispatchResultWithPostInfo { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; + let hash = message.using_encoded(sp_io::hashing::blake2_256); let message = (*message).try_into().map_err(|()| Error::::BadVersion)?; let value = (origin_location, message); ensure!(T::XcmExecuteFilter::contains(&value), Error::::Filtered); @@ -592,10 +893,12 @@ pub mod pallet { let outcome = T::XcmExecutor::execute_xcm_in_credit( origin_location, message, + hash, max_weight, max_weight, ); - let result = Ok(Some(outcome.weight_used().saturating_add(100_000_000)).into()); + let result = + Ok(Some(outcome.weight_used().saturating_add(T::WeightInfo::execute())).into()); Self::deposit_event(Event::Attempted(outcome)); result } @@ -607,7 +910,7 @@ pub mod pallet { /// - `location`: The destination that is being described. /// - `xcm_version`: The latest version of XCM that `location` supports. #[pallet::call_index(4)] - #[pallet::weight(100_000_000u64)] + #[pallet::weight(T::WeightInfo::force_xcm_version())] pub fn force_xcm_version( origin: OriginFor, location: Box, @@ -630,7 +933,7 @@ pub mod pallet { /// - `origin`: Must be Root. /// - `maybe_xcm_version`: The default XCM encoding version, or `None` to disable. #[pallet::call_index(5)] - #[pallet::weight(100_000_000u64)] + #[pallet::weight(T::WeightInfo::force_default_xcm_version())] pub fn force_default_xcm_version( origin: OriginFor, maybe_xcm_version: Option, @@ -645,7 +948,7 @@ pub mod pallet { /// - `origin`: Must be Root. /// - `location`: The location to which we should subscribe for XCM version notifications. #[pallet::call_index(6)] - #[pallet::weight(100_000_000u64)] + #[pallet::weight(T::WeightInfo::force_subscribe_version_notify())] pub fn force_subscribe_version_notify( origin: OriginFor, location: Box, @@ -669,7 +972,7 @@ pub mod pallet { /// - `location`: The location to which we are currently subscribed for XCM version /// notifications which we no longer desire. #[pallet::call_index(7)] - #[pallet::weight(100_000_000u64)] + #[pallet::weight(T::WeightInfo::force_unsubscribe_version_notify())] pub fn force_unsubscribe_version_notify( origin: OriginFor, location: Box, @@ -706,14 +1009,16 @@ pub mod pallet { /// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase. #[pallet::call_index(8)] #[pallet::weight({ - match ((*assets.clone()).try_into(), (*dest.clone()).try_into()) { + let maybe_assets: Result = (*assets.clone()).try_into(); + let maybe_dest: Result = (*dest.clone()).try_into(); + match (maybe_assets, maybe_dest) { (Ok(assets), Ok(dest)) => { use sp_std::vec; let mut message = Xcm(vec![ TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) } ]); - T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| Weight::from_ref_time(100_000_000.saturating_add(w))) - }, + T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::reserve_transfer_assets().saturating_add(w)) + } _ => Weight::MAX, } })] @@ -763,8 +1068,8 @@ pub mod pallet { WithdrawAsset(assets), InitiateTeleport { assets: Wild(All), dest, xcm: Xcm(vec![]) }, ]); - T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| Weight::from_ref_time(100_000_000.saturating_add(w))) - }, + T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::teleport_assets().saturating_add(w)) + } _ => Weight::MAX, } })] @@ -786,703 +1091,947 @@ pub mod pallet { ) } } +} - impl Pallet { - fn do_reserve_transfer_assets( - origin: OriginFor, - dest: Box, - beneficiary: Box, - assets: Box, - fee_asset_item: u32, - maybe_weight_limit: Option, - ) -> DispatchResult { - let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; - let dest = (*dest).try_into().map_err(|()| Error::::BadVersion)?; - let beneficiary: MultiLocation = - (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; - let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; - - ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); - let value = (origin_location, assets.drain()); - ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); - let (origin_location, assets) = value; - let ancestry = T::LocationInverter::ancestry(); - let fees = assets - .get(fee_asset_item as usize) - .ok_or(Error::::Empty)? - .clone() - .reanchored(&dest, &ancestry) - .map_err(|_| Error::::CannotReanchor)?; - let max_assets = assets.len() as u32; - let assets: MultiAssets = assets.into(); - let weight_limit = match maybe_weight_limit { - Some(weight_limit) => weight_limit, - None => { - let beneficiary = beneficiary.clone(); - let fees = fees.clone(); - let mut remote_message = Xcm(vec![ - ReserveAssetDeposited(assets.clone()), - ClearOrigin, - BuyExecution { fees, weight_limit: Limited(0) }, - DepositAsset { assets: Wild(All), max_assets, beneficiary }, - ]); - // use local weight for remote message and hope for the best. - let remote_weight = T::Weigher::weight(&mut remote_message) - .map_err(|()| Error::::UnweighableMessage)?; - Limited(remote_weight) - }, - }; - let xcm = Xcm(vec![ - BuyExecution { fees, weight_limit }, - DepositAsset { assets: Wild(All), max_assets, beneficiary }, - ]); - let mut message = Xcm(vec![TransferReserveAsset { assets, dest, xcm }]); - let weight = - T::Weigher::weight(&mut message).map_err(|()| Error::::UnweighableMessage)?; - let outcome = - T::XcmExecutor::execute_xcm_in_credit(origin_location, message, weight, weight); - Self::deposit_event(Event::Attempted(outcome)); - Ok(()) - } - - fn do_teleport_assets( - origin: OriginFor, - dest: Box, - beneficiary: Box, - assets: Box, - fee_asset_item: u32, - maybe_weight_limit: Option, - ) -> DispatchResult { - let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; - let dest = (*dest).try_into().map_err(|()| Error::::BadVersion)?; - let beneficiary: MultiLocation = - (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; - let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; - - ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); - let value = (origin_location, assets.drain()); - ensure!(T::XcmTeleportFilter::contains(&value), Error::::Filtered); - let (origin_location, assets) = value; - let ancestry = T::LocationInverter::ancestry(); - let fees = assets - .get(fee_asset_item as usize) - .ok_or(Error::::Empty)? - .clone() - .reanchored(&dest, &ancestry) - .map_err(|_| Error::::CannotReanchor)?; - let max_assets = assets.len() as u32; - let assets: MultiAssets = assets.into(); - let weight_limit = match maybe_weight_limit { - Some(weight_limit) => weight_limit, - None => { - let beneficiary = beneficiary.clone(); - let fees = fees.clone(); - let mut remote_message = Xcm(vec![ - ReceiveTeleportedAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees, weight_limit: Limited(0) }, - DepositAsset { assets: Wild(All), max_assets, beneficiary }, - ]); - // use local weight for remote message and hope for the best. - let remote_weight = T::Weigher::weight(&mut remote_message) - .map_err(|()| Error::::UnweighableMessage)?; - Limited(remote_weight) - }, - }; - let xcm = Xcm(vec![ - BuyExecution { fees, weight_limit }, - DepositAsset { assets: Wild(All), max_assets, beneficiary }, - ]); - let mut message = - Xcm(vec![WithdrawAsset(assets), InitiateTeleport { assets: Wild(All), dest, xcm }]); - let weight = - T::Weigher::weight(&mut message).map_err(|()| Error::::UnweighableMessage)?; - let outcome = - T::XcmExecutor::execute_xcm_in_credit(origin_location, message, weight, weight); - Self::deposit_event(Event::Attempted(outcome)); - Ok(()) - } +/// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic. +const MAX_ASSETS_FOR_TRANSFER: usize = 2; + +impl Pallet { + fn do_reserve_transfer_assets( + origin: OriginFor, + dest: Box, + beneficiary: Box, + assets: Box, + fee_asset_item: u32, + maybe_weight_limit: Option, + ) -> DispatchResult { + let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; + let dest = (*dest).try_into().map_err(|()| Error::::BadVersion)?; + let beneficiary: MultiLocation = + (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; + let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; + + ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); + let value = (origin_location, assets.into_inner()); + ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); + let (origin_location, assets) = value; + let context = T::UniversalLocation::get(); + let fees = assets + .get(fee_asset_item as usize) + .ok_or(Error::::Empty)? + .clone() + .reanchored(&dest, context) + .map_err(|_| Error::::CannotReanchor)?; + let max_assets = assets.len() as u32; + let assets: MultiAssets = assets.into(); + let weight_limit = match maybe_weight_limit { + Some(weight_limit) => weight_limit, + None => { + let fees = fees.clone(); + let mut remote_message = Xcm(vec![ + ReserveAssetDeposited(assets.clone()), + ClearOrigin, + BuyExecution { fees, weight_limit: Limited(Weight::zero()) }, + DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }, + ]); + // use local weight for remote message and hope for the best. + let remote_weight = T::Weigher::weight(&mut remote_message) + .map_err(|()| Error::::UnweighableMessage)?; + Limited(remote_weight) + }, + }; + let xcm = Xcm(vec![ + BuyExecution { fees, weight_limit }, + DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }, + ]); + let mut message = Xcm(vec![TransferReserveAsset { assets, dest, xcm }]); + let weight = + T::Weigher::weight(&mut message).map_err(|()| Error::::UnweighableMessage)?; + let hash = message.using_encoded(sp_io::hashing::blake2_256); + let outcome = + T::XcmExecutor::execute_xcm_in_credit(origin_location, message, hash, weight, weight); + Self::deposit_event(Event::Attempted(outcome)); + Ok(()) + } - /// Will always make progress, and will do its best not to use much more than `weight_cutoff` - /// in doing so. - pub(crate) fn check_xcm_version_change( - mut stage: VersionMigrationStage, - weight_cutoff: Weight, - ) -> (Weight, Option) { - let mut weight_used = Weight::zero(); + fn do_teleport_assets( + origin: OriginFor, + dest: Box, + beneficiary: Box, + assets: Box, + fee_asset_item: u32, + maybe_weight_limit: Option, + ) -> DispatchResult { + let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; + let dest = (*dest).try_into().map_err(|()| Error::::BadVersion)?; + let beneficiary: MultiLocation = + (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; + let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; + + ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); + let value = (origin_location, assets.into_inner()); + ensure!(T::XcmTeleportFilter::contains(&value), Error::::Filtered); + let (origin_location, assets) = value; + let context = T::UniversalLocation::get(); + let fees = assets + .get(fee_asset_item as usize) + .ok_or(Error::::Empty)? + .clone() + .reanchored(&dest, context) + .map_err(|_| Error::::CannotReanchor)?; + let max_assets = assets.len() as u32; + let assets: MultiAssets = assets.into(); + let weight_limit = match maybe_weight_limit { + Some(weight_limit) => weight_limit, + None => { + let fees = fees.clone(); + let mut remote_message = Xcm(vec![ + ReceiveTeleportedAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees, weight_limit: Limited(Weight::zero()) }, + DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }, + ]); + // use local weight for remote message and hope for the best. + let remote_weight = T::Weigher::weight(&mut remote_message) + .map_err(|()| Error::::UnweighableMessage)?; + Limited(remote_weight) + }, + }; + let xcm = Xcm(vec![ + BuyExecution { fees, weight_limit }, + DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }, + ]); + let mut message = + Xcm(vec![WithdrawAsset(assets), InitiateTeleport { assets: Wild(All), dest, xcm }]); + let weight = + T::Weigher::weight(&mut message).map_err(|()| Error::::UnweighableMessage)?; + let hash = message.using_encoded(sp_io::hashing::blake2_256); + let outcome = + T::XcmExecutor::execute_xcm_in_credit(origin_location, message, hash, weight, weight); + Self::deposit_event(Event::Attempted(outcome)); + Ok(()) + } - // TODO: Correct weights for the components of this: - let todo_sv_migrate_weight: Weight = T::DbWeight::get().reads_writes(1, 1); - let todo_vn_migrate_weight: Weight = T::DbWeight::get().reads_writes(1, 1); - let todo_vnt_already_notified_weight: Weight = T::DbWeight::get().reads(1); - let todo_vnt_notify_weight: Weight = T::DbWeight::get().reads_writes(1, 3); - let todo_vnt_migrate_weight: Weight = T::DbWeight::get().reads_writes(1, 1); - let todo_vnt_migrate_fail_weight: Weight = T::DbWeight::get().reads_writes(1, 1); - let todo_vnt_notify_migrate_weight: Weight = T::DbWeight::get().reads_writes(1, 3); - - use VersionMigrationStage::*; - - if stage == MigrateSupportedVersion { - // We assume that supported XCM version only ever increases, so just cycle through lower - // XCM versioned from the current. - for v in 0..XCM_VERSION { - for (old_key, value) in SupportedVersion::::drain_prefix(v) { - if let Ok(new_key) = old_key.into_latest() { - SupportedVersion::::insert(XCM_VERSION, new_key, value); - } - weight_used.saturating_accrue(todo_sv_migrate_weight); - if weight_used.any_gte(weight_cutoff) { - return (weight_used, Some(stage)) - } + /// Will always make progress, and will do its best not to use much more than `weight_cutoff` + /// in doing so. + pub(crate) fn check_xcm_version_change( + mut stage: VersionMigrationStage, + weight_cutoff: Weight, + ) -> (Weight, Option) { + let mut weight_used = Weight::zero(); + + let sv_migrate_weight = T::WeightInfo::migrate_supported_version(); + let vn_migrate_weight = T::WeightInfo::migrate_version_notifiers(); + let vnt_already_notified_weight = T::WeightInfo::already_notified_target(); + let vnt_notify_weight = T::WeightInfo::notify_current_targets(); + let vnt_migrate_weight = T::WeightInfo::migrate_version_notify_targets(); + let vnt_migrate_fail_weight = T::WeightInfo::notify_target_migration_fail(); + let vnt_notify_migrate_weight = T::WeightInfo::migrate_and_notify_old_targets(); + + use VersionMigrationStage::*; + + if stage == MigrateSupportedVersion { + // We assume that supported XCM version only ever increases, so just cycle through lower + // XCM versioned from the current. + for v in 0..XCM_VERSION { + for (old_key, value) in SupportedVersion::::drain_prefix(v) { + if let Ok(new_key) = old_key.into_latest() { + SupportedVersion::::insert(XCM_VERSION, new_key, value); + } + weight_used.saturating_accrue(sv_migrate_weight); + if weight_used.any_gte(weight_cutoff) { + return (weight_used, Some(stage)) } } - stage = MigrateVersionNotifiers; } - if stage == MigrateVersionNotifiers { - for v in 0..XCM_VERSION { - for (old_key, value) in VersionNotifiers::::drain_prefix(v) { - if let Ok(new_key) = old_key.into_latest() { - VersionNotifiers::::insert(XCM_VERSION, new_key, value); - } - weight_used.saturating_accrue(todo_vn_migrate_weight); - if weight_used.any_gte(weight_cutoff) { - return (weight_used, Some(stage)) - } + stage = MigrateVersionNotifiers; + } + if stage == MigrateVersionNotifiers { + for v in 0..XCM_VERSION { + for (old_key, value) in VersionNotifiers::::drain_prefix(v) { + if let Ok(new_key) = old_key.into_latest() { + VersionNotifiers::::insert(XCM_VERSION, new_key, value); + } + weight_used.saturating_accrue(vn_migrate_weight); + if weight_used.any_gte(weight_cutoff) { + return (weight_used, Some(stage)) } } - stage = NotifyCurrentTargets(None); } + stage = NotifyCurrentTargets(None); + } - let xcm_version = T::AdvertisedXcmVersion::get(); + let xcm_version = T::AdvertisedXcmVersion::get(); - if let NotifyCurrentTargets(maybe_last_raw_key) = stage { - let mut iter = match maybe_last_raw_key { - Some(k) => VersionNotifyTargets::::iter_prefix_from(XCM_VERSION, k), - None => VersionNotifyTargets::::iter_prefix(XCM_VERSION), + if let NotifyCurrentTargets(maybe_last_raw_key) = stage { + let mut iter = match maybe_last_raw_key { + Some(k) => VersionNotifyTargets::::iter_prefix_from(XCM_VERSION, k), + None => VersionNotifyTargets::::iter_prefix(XCM_VERSION), + }; + while let Some((key, value)) = iter.next() { + let (query_id, max_weight, target_xcm_version) = value; + let new_key: MultiLocation = match key.clone().try_into() { + Ok(k) if target_xcm_version != xcm_version => k, + _ => { + // We don't early return here since we need to be certain that we + // make some progress. + weight_used.saturating_accrue(vnt_already_notified_weight); + continue + }, }; - while let Some((key, value)) = iter.next() { + let response = Response::Version(xcm_version); + let message = + Xcm(vec![QueryResponse { query_id, response, max_weight, querier: None }]); + let event = match send_xcm::(new_key, message) { + Ok((_hash, cost)) => { + let value = (query_id, max_weight, xcm_version); + VersionNotifyTargets::::insert(XCM_VERSION, key, value); + Event::VersionChangeNotified(new_key, xcm_version, cost) + }, + Err(e) => { + VersionNotifyTargets::::remove(XCM_VERSION, key); + Event::NotifyTargetSendFail(new_key, query_id, e.into()) + }, + }; + Self::deposit_event(event); + weight_used.saturating_accrue(vnt_notify_weight); + if weight_used.any_gte(weight_cutoff) { + let last = Some(iter.last_raw_key().into()); + return (weight_used, Some(NotifyCurrentTargets(last))) + } + } + stage = MigrateAndNotifyOldTargets; + } + if stage == MigrateAndNotifyOldTargets { + for v in 0..XCM_VERSION { + for (old_key, value) in VersionNotifyTargets::::drain_prefix(v) { let (query_id, max_weight, target_xcm_version) = value; - let new_key: MultiLocation = match key.clone().try_into() { - Ok(k) if target_xcm_version != xcm_version => k, - _ => { - // We don't early return here since we need to be certain that we - // make some progress. - weight_used.saturating_accrue(todo_vnt_already_notified_weight); + let new_key = match MultiLocation::try_from(old_key.clone()) { + Ok(k) => k, + Err(()) => { + Self::deposit_event(Event::NotifyTargetMigrationFail(old_key, value.0)); + weight_used.saturating_accrue(vnt_migrate_fail_weight); + if weight_used.any_gte(weight_cutoff) { + return (weight_used, Some(stage)) + } continue }, }; - let response = Response::Version(xcm_version); - let message = Xcm(vec![QueryResponse { query_id, response, max_weight }]); - let event = match T::XcmRouter::send_xcm(new_key.clone(), message) { - Ok(()) => { - let value = (query_id, max_weight, xcm_version); - VersionNotifyTargets::::insert(XCM_VERSION, key, value); - Event::VersionChangeNotified(new_key, xcm_version) - }, - Err(e) => { - VersionNotifyTargets::::remove(XCM_VERSION, key); - Event::NotifyTargetSendFail(new_key, query_id, e.into()) - }, - }; - Self::deposit_event(event); - weight_used.saturating_accrue(todo_vnt_notify_weight); - if weight_used.any_gte(weight_cutoff) { - let last = Some(iter.last_raw_key().into()); - return (weight_used, Some(NotifyCurrentTargets(last))) - } - } - stage = MigrateAndNotifyOldTargets; - } - if stage == MigrateAndNotifyOldTargets { - for v in 0..XCM_VERSION { - for (old_key, value) in VersionNotifyTargets::::drain_prefix(v) { - let (query_id, max_weight, target_xcm_version) = value; - let new_key = match MultiLocation::try_from(old_key.clone()) { - Ok(k) => k, - Err(()) => { - Self::deposit_event(Event::NotifyTargetMigrationFail( - old_key, value.0, - )); - weight_used.saturating_accrue(todo_vnt_migrate_fail_weight); - if weight_used.any_gte(weight_cutoff) { - return (weight_used, Some(stage)) - } - continue + + let versioned_key = LatestVersionedMultiLocation(&new_key); + if target_xcm_version == xcm_version { + VersionNotifyTargets::::insert(XCM_VERSION, versioned_key, value); + weight_used.saturating_accrue(vnt_migrate_weight); + } else { + // Need to notify target. + let response = Response::Version(xcm_version); + let message = Xcm(vec![QueryResponse { + query_id, + response, + max_weight, + querier: None, + }]); + let event = match send_xcm::(new_key, message) { + Ok((_hash, cost)) => { + VersionNotifyTargets::::insert( + XCM_VERSION, + versioned_key, + (query_id, max_weight, xcm_version), + ); + Event::VersionChangeNotified(new_key, xcm_version, cost) }, + Err(e) => Event::NotifyTargetSendFail(new_key, query_id, e.into()), }; - - let versioned_key = LatestVersionedMultiLocation(&new_key); - if target_xcm_version == xcm_version { - VersionNotifyTargets::::insert(XCM_VERSION, versioned_key, value); - weight_used.saturating_accrue(todo_vnt_migrate_weight); - } else { - // Need to notify target. - let response = Response::Version(xcm_version); - let message = - Xcm(vec![QueryResponse { query_id, response, max_weight }]); - let event = match T::XcmRouter::send_xcm(new_key.clone(), message) { - Ok(()) => { - VersionNotifyTargets::::insert( - XCM_VERSION, - versioned_key, - (query_id, max_weight, xcm_version), - ); - Event::VersionChangeNotified(new_key, xcm_version) - }, - Err(e) => Event::NotifyTargetSendFail(new_key, query_id, e.into()), - }; - Self::deposit_event(event); - weight_used.saturating_accrue(todo_vnt_notify_migrate_weight); - } - if weight_used.any_gte(weight_cutoff) { - return (weight_used, Some(stage)) - } + Self::deposit_event(event); + weight_used.saturating_accrue(vnt_notify_migrate_weight); + } + if weight_used.any_gte(weight_cutoff) { + return (weight_used, Some(stage)) } } } - (weight_used, None) } + (weight_used, None) + } - /// Request that `dest` informs us of its version. - pub fn request_version_notify(dest: impl Into) -> XcmResult { - let dest = dest.into(); - let versioned_dest = VersionedMultiLocation::from(dest.clone()); - let already = VersionNotifiers::::contains_key(XCM_VERSION, &versioned_dest); - ensure!(!already, XcmError::InvalidLocation); - let query_id = QueryCounter::::mutate(|q| { - let r = *q; - q.saturating_inc(); - r - }); - // TODO #3735: Correct weight. - let instruction = SubscribeVersion { query_id, max_response_weight: 0 }; - T::XcmRouter::send_xcm(dest, Xcm(vec![instruction]))?; - VersionNotifiers::::insert(XCM_VERSION, &versioned_dest, query_id); - let query_status = - QueryStatus::VersionNotifier { origin: versioned_dest, is_active: false }; - Queries::::insert(query_id, query_status); - Ok(()) - } + /// Request that `dest` informs us of its version. + pub fn request_version_notify(dest: impl Into) -> XcmResult { + let dest = dest.into(); + let versioned_dest = VersionedMultiLocation::from(dest); + let already = VersionNotifiers::::contains_key(XCM_VERSION, &versioned_dest); + ensure!(!already, XcmError::InvalidLocation); + let query_id = QueryCounter::::mutate(|q| { + let r = *q; + q.saturating_inc(); + r + }); + // TODO #3735: Correct weight. + let instruction = SubscribeVersion { query_id, max_response_weight: Weight::zero() }; + let (_hash, cost) = send_xcm::(dest, Xcm(vec![instruction]))?; + Self::deposit_event(Event::VersionNotifyRequested(dest, cost)); + VersionNotifiers::::insert(XCM_VERSION, &versioned_dest, query_id); + let query_status = + QueryStatus::VersionNotifier { origin: versioned_dest, is_active: false }; + Queries::::insert(query_id, query_status); + Ok(()) + } - /// Request that `dest` ceases informing us of its version. - pub fn unrequest_version_notify(dest: impl Into) -> XcmResult { - let dest = dest.into(); - let versioned_dest = LatestVersionedMultiLocation(&dest); - let query_id = VersionNotifiers::::take(XCM_VERSION, versioned_dest) - .ok_or(XcmError::InvalidLocation)?; - T::XcmRouter::send_xcm(dest.clone(), Xcm(vec![UnsubscribeVersion]))?; - Queries::::remove(query_id); - Ok(()) - } + /// Request that `dest` ceases informing us of its version. + pub fn unrequest_version_notify(dest: impl Into) -> XcmResult { + let dest = dest.into(); + let versioned_dest = LatestVersionedMultiLocation(&dest); + let query_id = VersionNotifiers::::take(XCM_VERSION, versioned_dest) + .ok_or(XcmError::InvalidLocation)?; + let (_hash, cost) = send_xcm::(dest, Xcm(vec![UnsubscribeVersion]))?; + Self::deposit_event(Event::VersionNotifyUnrequested(dest, cost)); + Queries::::remove(query_id); + Ok(()) + } - /// Relay an XCM `message` from a given `interior` location in this context to a given `dest` - /// location. A null `dest` is not handled. - pub fn send_xcm( - interior: impl Into, - dest: impl Into, - mut message: Xcm<()>, - ) -> Result<(), SendError> { - let interior = interior.into(); - let dest = dest.into(); - if interior != Junctions::Here { - message.0.insert(0, DescendOrigin(interior)) - }; - log::trace!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message); - T::XcmRouter::send_xcm(dest, message) + /// Relay an XCM `message` from a given `interior` location in this context to a given `dest` + /// location. The `fee_payer` is charged for the delivery unless `None` in which case fees + /// are not charged (and instead borne by the chain). + pub fn send_xcm( + interior: impl Into, + dest: impl Into, + mut message: Xcm<()>, + ) -> Result { + let interior = interior.into(); + let dest = dest.into(); + let maybe_fee_payer = if interior != Junctions::Here { + message.0.insert(0, DescendOrigin(interior)); + Some(interior.into()) + } else { + None + }; + log::debug!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message); + let (ticket, price) = validate_send::(dest, message)?; + if let Some(fee_payer) = maybe_fee_payer { + Self::charge_fees(fee_payer, price).map_err(|_| SendError::Fees)?; } + T::XcmRouter::deliver(ticket) + } - pub fn check_account() -> T::AccountId { - const ID: PalletId = PalletId(*b"py/xcmch"); - AccountIdConversion::::into_account_truncating(&ID) - } + pub fn check_account() -> T::AccountId { + const ID: PalletId = PalletId(*b"py/xcmch"); + AccountIdConversion::::into_account_truncating(&ID) + } - fn do_new_query( - responder: impl Into, - maybe_notify: Option<(u8, u8)>, - timeout: T::BlockNumber, - ) -> u64 { - QueryCounter::::mutate(|q| { - let r = *q; - q.saturating_inc(); - Queries::::insert( - r, - QueryStatus::Pending { - responder: responder.into().into(), - maybe_notify, - timeout, - }, - ); - r - }) - } + /// Create a new expectation of a query response with the querier being here. + fn do_new_query( + responder: impl Into, + maybe_notify: Option<(u8, u8)>, + timeout: T::BlockNumber, + match_querier: impl Into, + ) -> u64 { + QueryCounter::::mutate(|q| { + let r = *q; + q.saturating_inc(); + Queries::::insert( + r, + QueryStatus::Pending { + responder: responder.into().into(), + maybe_match_querier: Some(match_querier.into().into()), + maybe_notify, + timeout, + }, + ); + r + }) + } - /// Consume `message` and return another which is equivalent to it except that it reports - /// back the outcome. - /// - /// - `message`: The message whose outcome should be reported. - /// - `responder`: The origin from which a response should be expected. - /// - `timeout`: The block number after which it is permissible for `notify` not to be - /// called even if a response is received. - /// - /// `report_outcome` may return an error if the `responder` is not invertible. - /// - /// To check the status of the query, use `fn query()` passing the resultant `QueryId` - /// value. - pub fn report_outcome( - message: &mut Xcm<()>, - responder: impl Into, - timeout: T::BlockNumber, - ) -> Result { - let responder = responder.into(); - let dest = T::LocationInverter::invert_location(&responder) - .map_err(|()| XcmError::MultiLocationNotInvertible)?; - let query_id = Self::new_query(responder, timeout); - let report_error = Xcm(vec![ReportError { dest, query_id, max_response_weight: 0 }]); - message.0.insert(0, SetAppendix(report_error)); - Ok(query_id) - } + /// Consume `message` and return another which is equivalent to it except that it reports + /// back the outcome. + /// + /// - `message`: The message whose outcome should be reported. + /// - `responder`: The origin from which a response should be expected. + /// - `timeout`: The block number after which it is permissible for `notify` not to be + /// called even if a response is received. + /// + /// `report_outcome` may return an error if the `responder` is not invertible. + /// + /// It is assumed that the querier of the response will be `Here`. + /// + /// To check the status of the query, use `fn query()` passing the resultant `QueryId` + /// value. + pub fn report_outcome( + message: &mut Xcm<()>, + responder: impl Into, + timeout: T::BlockNumber, + ) -> Result { + let responder = responder.into(); + let destination = T::UniversalLocation::get() + .invert_target(&responder) + .map_err(|()| XcmError::LocationNotInvertible)?; + let query_id = Self::new_query(responder, timeout, Here); + let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() }; + let report_error = Xcm(vec![ReportError(response_info)]); + message.0.insert(0, SetAppendix(report_error)); + Ok(query_id) + } - /// Consume `message` and return another which is equivalent to it except that it reports - /// back the outcome and dispatches `notify` on this chain. - /// - /// - `message`: The message whose outcome should be reported. - /// - `responder`: The origin from which a response should be expected. - /// - `notify`: A dispatchable function which will be called once the outcome of `message` - /// is known. It may be a dispatchable in any pallet of the local chain, but other than - /// the usual origin, it must accept exactly two arguments: `query_id: QueryId` and - /// `outcome: Response`, and in that order. It should expect that the origin is - /// `Origin::Response` and will contain the responder's location. - /// - `timeout`: The block number after which it is permissible for `notify` not to be - /// called even if a response is received. - /// - /// `report_outcome_notify` may return an error if the `responder` is not invertible. - /// - /// NOTE: `notify` gets called as part of handling an incoming message, so it should be - /// lightweight. Its weight is estimated during this function and stored ready for - /// weighing `ReportOutcome` on the way back. If it turns out to be heavier once it returns - /// then reporting the outcome will fail. Futhermore if the estimate is too high, then it - /// may be put in the overweight queue and need to be manually executed. - pub fn report_outcome_notify( - message: &mut Xcm<()>, - responder: impl Into, - notify: impl Into<::RuntimeCall>, - timeout: T::BlockNumber, - ) -> Result<(), XcmError> { - let responder = responder.into(); - let dest = T::LocationInverter::invert_location(&responder) - .map_err(|()| XcmError::MultiLocationNotInvertible)?; - let notify: ::RuntimeCall = notify.into(); - let max_response_weight = notify.get_dispatch_info().weight; - let query_id = Self::new_notify_query(responder, notify, timeout); - let report_error = Xcm(vec![ReportError { - dest, - query_id, - max_response_weight: max_response_weight.ref_time(), - }]); - message.0.insert(0, SetAppendix(report_error)); - Ok(()) - } + /// Consume `message` and return another which is equivalent to it except that it reports + /// back the outcome and dispatches `notify` on this chain. + /// + /// - `message`: The message whose outcome should be reported. + /// - `responder`: The origin from which a response should be expected. + /// - `notify`: A dispatchable function which will be called once the outcome of `message` + /// is known. It may be a dispatchable in any pallet of the local chain, but other than + /// the usual origin, it must accept exactly two arguments: `query_id: QueryId` and + /// `outcome: Response`, and in that order. It should expect that the origin is + /// `Origin::Response` and will contain the responder's location. + /// - `timeout`: The block number after which it is permissible for `notify` not to be + /// called even if a response is received. + /// + /// `report_outcome_notify` may return an error if the `responder` is not invertible. + /// + /// It is assumed that the querier of the response will be `Here`. + /// + /// NOTE: `notify` gets called as part of handling an incoming message, so it should be + /// lightweight. Its weight is estimated during this function and stored ready for + /// weighing `ReportOutcome` on the way back. If it turns out to be heavier once it returns + /// then reporting the outcome will fail. Futhermore if the estimate is too high, then it + /// may be put in the overweight queue and need to be manually executed. + pub fn report_outcome_notify( + message: &mut Xcm<()>, + responder: impl Into, + notify: impl Into<::RuntimeCall>, + timeout: T::BlockNumber, + ) -> Result<(), XcmError> { + let responder = responder.into(); + let destination = T::UniversalLocation::get() + .invert_target(&responder) + .map_err(|()| XcmError::LocationNotInvertible)?; + let notify: ::RuntimeCall = notify.into(); + let max_weight = notify.get_dispatch_info().weight; + let query_id = Self::new_notify_query(responder, notify, timeout, Here); + let response_info = QueryResponseInfo { destination, query_id, max_weight }; + let report_error = Xcm(vec![ReportError(response_info)]); + message.0.insert(0, SetAppendix(report_error)); + Ok(()) + } - /// Attempt to create a new query ID and register it as a query that is yet to respond. - pub fn new_query(responder: impl Into, timeout: T::BlockNumber) -> u64 { - Self::do_new_query(responder, None, timeout) - } + /// Attempt to create a new query ID and register it as a query that is yet to respond. + pub fn new_query( + responder: impl Into, + timeout: T::BlockNumber, + match_querier: impl Into, + ) -> u64 { + Self::do_new_query(responder, None, timeout, match_querier) + } - /// Attempt to create a new query ID and register it as a query that is yet to respond, and - /// which will call a dispatchable when a response happens. - pub fn new_notify_query( - responder: impl Into, - notify: impl Into<::RuntimeCall>, - timeout: T::BlockNumber, - ) -> u64 { - let notify = - notify.into().using_encoded(|mut bytes| Decode::decode(&mut bytes)).expect( - "decode input is output of Call encode; Call guaranteed to have two enums; qed", - ); - Self::do_new_query(responder, Some(notify), timeout) + /// Attempt to create a new query ID and register it as a query that is yet to respond, and + /// which will call a dispatchable when a response happens. + pub fn new_notify_query( + responder: impl Into, + notify: impl Into<::RuntimeCall>, + timeout: T::BlockNumber, + match_querier: impl Into, + ) -> u64 { + let notify = notify.into().using_encoded(|mut bytes| Decode::decode(&mut bytes)).expect( + "decode input is output of Call encode; Call guaranteed to have two enums; qed", + ); + Self::do_new_query(responder, Some(notify), timeout, match_querier) + } + + /// Attempt to remove and return the response of query with ID `query_id`. + /// + /// Returns `None` if the response is not (yet) available. + pub fn take_response(query_id: QueryId) -> Option<(Response, T::BlockNumber)> { + if let Some(QueryStatus::Ready { response, at }) = Queries::::get(query_id) { + let response = response.try_into().ok()?; + Queries::::remove(query_id); + Self::deposit_event(Event::ResponseTaken(query_id)); + Some((response, at)) + } else { + None } + } - /// Attempt to remove and return the response of query with ID `query_id`. - /// - /// Returns `None` if the response is not (yet) available. - pub fn take_response(query_id: QueryId) -> Option<(Response, T::BlockNumber)> { - if let Some(QueryStatus::Ready { response, at }) = Queries::::get(query_id) { - let response = response.try_into().ok()?; - Queries::::remove(query_id); - Self::deposit_event(Event::ResponseTaken(query_id)); - Some((response, at)) + /// Note that a particular destination to whom we would like to send a message is unknown + /// and queue it for version discovery. + fn note_unknown_version(dest: &MultiLocation) { + log::trace!( + target: "xcm::pallet_xcm::note_unknown_version", + "XCM version is unknown for destination: {:?}", + dest, + ); + let versioned_dest = VersionedMultiLocation::from(*dest); + VersionDiscoveryQueue::::mutate(|q| { + if let Some(index) = q.iter().position(|i| &i.0 == &versioned_dest) { + // exists - just bump the count. + q[index].1.saturating_inc(); } else { - None + let _ = q.try_push((versioned_dest, 1)); } + }); + } + + /// Withdraw given `assets` from the given `location` and pay as XCM fees. + /// + /// Fails if: + /// - the `assets` are not known on this chain; + /// - the `assets` cannot be withdrawn with that location as the Origin. + fn charge_fees(location: MultiLocation, assets: MultiAssets) -> DispatchResult { + T::XcmExecutor::charge_fees(location, assets.clone()) + .map_err(|_| Error::::FeesNotMet)?; + Self::deposit_event(Event::FeesPaid(location, assets)); + Ok(()) + } +} + +pub struct LockTicket { + sovereign_account: T::AccountId, + amount: BalanceOf, + unlocker: MultiLocation, + item_index: Option, +} + +impl xcm_executor::traits::Enact for LockTicket { + fn enact(self) -> Result<(), xcm_executor::traits::LockError> { + use xcm_executor::traits::LockError::UnexpectedState; + let mut locks = LockedFungibles::::get(&self.sovereign_account).unwrap_or_default(); + match self.item_index { + Some(index) => { + ensure!(locks.len() > index, UnexpectedState); + ensure!(locks[index].1.try_as::<_>() == Ok(&self.unlocker), UnexpectedState); + locks[index].0 = locks[index].0.max(self.amount); + }, + None => { + locks + .try_push((self.amount, self.unlocker.into())) + .map_err(|(_balance, _location)| UnexpectedState)?; + }, } + LockedFungibles::::insert(&self.sovereign_account, locks); + T::Currency::extend_lock( + *b"py/xcmlk", + &self.sovereign_account, + self.amount, + WithdrawReasons::all(), + ); + Ok(()) + } +} - /// Note that a particular destination to whom we would like to send a message is unknown - /// and queue it for version discovery. - fn note_unknown_version(dest: &MultiLocation) { - log::trace!( - target: "xcm::pallet_xcm::note_unknown_version", - "XCM version is unknown for destination: {:?}", - dest, - ); - let versioned_dest = VersionedMultiLocation::from(dest.clone()); - VersionDiscoveryQueue::::mutate(|q| { - if let Some(index) = q.iter().position(|i| &i.0 == &versioned_dest) { - // exists - just bump the count. - q[index].1.saturating_inc(); - } else { - let _ = q.try_push((versioned_dest, 1)); +pub struct UnlockTicket { + sovereign_account: T::AccountId, + amount: BalanceOf, + unlocker: MultiLocation, +} + +impl xcm_executor::traits::Enact for UnlockTicket { + fn enact(self) -> Result<(), xcm_executor::traits::LockError> { + use xcm_executor::traits::LockError::UnexpectedState; + let mut locks = + LockedFungibles::::get(&self.sovereign_account).ok_or(UnexpectedState)?; + let mut maybe_remove_index = None; + let mut locked = BalanceOf::::zero(); + let mut found = false; + // We could just as well do with with an into_iter, filter_map and collect, however this way + // avoids making an allocation. + for (i, x) in locks.iter_mut().enumerate() { + if x.1.try_as::<_>().defensive() == Ok(&self.unlocker) { + x.0 = x.0.saturating_sub(self.amount); + if x.0.is_zero() { + maybe_remove_index = Some(i); } - }); + found = true; + } + locked = locked.max(x.0); } + ensure!(found, UnexpectedState); + if let Some(remove_index) = maybe_remove_index { + locks.swap_remove(remove_index); + } + LockedFungibles::::insert(&self.sovereign_account, locks); + let reasons = WithdrawReasons::all(); + T::Currency::set_lock(*b"py/xcmlk", &self.sovereign_account, locked, reasons); + Ok(()) } +} - impl WrapVersion for Pallet { - fn wrap_version( - dest: &MultiLocation, - xcm: impl Into>, - ) -> Result, ()> { - SupportedVersion::::get(XCM_VERSION, LatestVersionedMultiLocation(dest)) - .or_else(|| { - Self::note_unknown_version(dest); - SafeXcmVersion::::get() - }) - .ok_or_else(|| { - log::trace!( - target: "xcm::pallet_xcm::wrap_version", - "Could not determine a version to wrap XCM for destination: {:?}", - dest, - ); - () - }) - .and_then(|v| xcm.into().into_version(v.min(XCM_VERSION))) +pub struct ReduceTicket { + key: (u32, T::AccountId, VersionedAssetId), + amount: u128, + locker: VersionedMultiLocation, + owner: VersionedMultiLocation, +} + +impl xcm_executor::traits::Enact for ReduceTicket { + fn enact(self) -> Result<(), xcm_executor::traits::LockError> { + use xcm_executor::traits::LockError::UnexpectedState; + let mut record = RemoteLockedFungibles::::get(&self.key).ok_or(UnexpectedState)?; + ensure!(self.locker == record.locker && self.owner == record.owner, UnexpectedState); + ensure!(record.users == 0, UnexpectedState); + record.amount = record.amount.checked_sub(self.amount).ok_or(UnexpectedState)?; + if record.amount == 0 { + RemoteLockedFungibles::::remove(&self.key); + } else { + RemoteLockedFungibles::::insert(&self.key, &record); } + Ok(()) } +} - impl VersionChangeNotifier for Pallet { - /// Start notifying `location` should the XCM version of this chain change. - /// - /// When it does, this type should ensure a `QueryResponse` message is sent with the given - /// `query_id` & `max_weight` and with a `response` of `Repsonse::Version`. This should happen - /// until/unless `stop` is called with the correct `query_id`. - /// - /// If the `location` has an ongoing notification and when this function is called, then an - /// error should be returned. - fn start(dest: &MultiLocation, query_id: QueryId, max_weight: u64) -> XcmResult { - let versioned_dest = LatestVersionedMultiLocation(dest); - let already = VersionNotifyTargets::::contains_key(XCM_VERSION, versioned_dest); - ensure!(!already, XcmError::InvalidLocation); - - let xcm_version = T::AdvertisedXcmVersion::get(); - let response = Response::Version(xcm_version); - let instruction = QueryResponse { query_id, response, max_weight }; - T::XcmRouter::send_xcm(dest.clone(), Xcm(vec![instruction]))?; - - let value = (query_id, max_weight, xcm_version); - VersionNotifyTargets::::insert(XCM_VERSION, versioned_dest, value); - Ok(()) - } +impl xcm_executor::traits::AssetLock for Pallet { + type LockTicket = LockTicket; + type UnlockTicket = UnlockTicket; + type ReduceTicket = ReduceTicket; + + fn prepare_lock( + unlocker: MultiLocation, + asset: MultiAsset, + owner: MultiLocation, + ) -> Result, xcm_executor::traits::LockError> { + use xcm_executor::traits::LockError::*; + let sovereign_account = T::SovereignAccountOf::convert_ref(&owner).map_err(|_| BadOwner)?; + let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?; + ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned); + let locks = LockedFungibles::::get(&sovereign_account).unwrap_or_default(); + let item_index = locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker)); + ensure!(item_index.is_some() || locks.len() < T::MaxLockers::get() as usize, NoResources); + Ok(LockTicket { sovereign_account, amount, unlocker, item_index }) + } - /// Stop notifying `location` should the XCM change. This is a no-op if there was never a - /// subscription. - fn stop(dest: &MultiLocation) -> XcmResult { - VersionNotifyTargets::::remove(XCM_VERSION, LatestVersionedMultiLocation(dest)); - Ok(()) - } + fn prepare_unlock( + unlocker: MultiLocation, + asset: MultiAsset, + owner: MultiLocation, + ) -> Result, xcm_executor::traits::LockError> { + use xcm_executor::traits::LockError::*; + let sovereign_account = T::SovereignAccountOf::convert_ref(&owner).map_err(|_| BadOwner)?; + let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?; + ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned); + let locks = LockedFungibles::::get(&sovereign_account).unwrap_or_default(); + let item_index = + locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker)).ok_or(NotLocked)?; + ensure!(locks[item_index].0 >= amount, NotLocked); + Ok(UnlockTicket { sovereign_account, amount, unlocker }) + } - /// Return true if a location is subscribed to XCM version changes. - fn is_subscribed(dest: &MultiLocation) -> bool { - let versioned_dest = LatestVersionedMultiLocation(dest); - VersionNotifyTargets::::contains_key(XCM_VERSION, versioned_dest) + fn note_unlockable( + locker: MultiLocation, + asset: MultiAsset, + mut owner: MultiLocation, + ) -> Result<(), xcm_executor::traits::LockError> { + use xcm_executor::traits::LockError::*; + ensure!(T::TrustedLockers::contains(&locker, &asset), NotTrusted); + let amount = match asset.fun { + Fungible(a) => a, + NonFungible(_) => return Err(Unimplemented), + }; + owner.remove_network_id(); + let account = T::SovereignAccountOf::convert_ref(&owner).map_err(|_| BadOwner)?; + let locker = locker.into(); + let owner = owner.into(); + let id: VersionedAssetId = asset.id.into(); + let key = (XCM_VERSION, account, id); + let mut record = RemoteLockedFungibleRecord { amount, owner, locker, users: 0 }; + if let Some(old) = RemoteLockedFungibles::::get(&key) { + // Make sure that the new record wouldn't clobber any old data. + ensure!(old.locker == record.locker && old.owner == record.owner, WouldClobber); + record.users = old.users; + record.amount = record.amount.max(old.amount); } + RemoteLockedFungibles::::insert(&key, record); + Ok(()) } - impl DropAssets for Pallet { - fn drop_assets(origin: &MultiLocation, assets: Assets) -> u64 { - if assets.is_empty() { - return 0 - } - let versioned = VersionedMultiAssets::from(MultiAssets::from(assets)); - let hash = BlakeTwo256::hash_of(&(&origin, &versioned)); - AssetTraps::::mutate(hash, |n| *n += 1); - Self::deposit_event(Event::AssetsTrapped(hash, origin.clone(), versioned)); - // TODO #3735: Put the real weight in there. - 0 + fn prepare_reduce_unlockable( + locker: MultiLocation, + asset: MultiAsset, + mut owner: MultiLocation, + ) -> Result { + use xcm_executor::traits::LockError::*; + let amount = match asset.fun { + Fungible(a) => a, + NonFungible(_) => return Err(Unimplemented), + }; + owner.remove_network_id(); + let sovereign_account = T::SovereignAccountOf::convert_ref(&owner).map_err(|_| BadOwner)?; + let locker = locker.into(); + let owner = owner.into(); + let id: VersionedAssetId = asset.id.into(); + let key = (XCM_VERSION, sovereign_account, id); + + let record = RemoteLockedFungibles::::get(&key).ok_or(NotLocked)?; + // Make sure that the record contains what we expect and there's enough to unlock. + ensure!(locker == record.locker && owner == record.owner, WouldClobber); + ensure!(record.users == 0, InUse); + ensure!(record.amount >= amount, NotEnoughLocked); + Ok(ReduceTicket { key, amount, locker, owner }) + } +} + +impl WrapVersion for Pallet { + fn wrap_version( + dest: &MultiLocation, + xcm: impl Into>, + ) -> Result, ()> { + SupportedVersion::::get(XCM_VERSION, LatestVersionedMultiLocation(dest)) + .or_else(|| { + Self::note_unknown_version(dest); + SafeXcmVersion::::get() + }) + .ok_or_else(|| { + log::trace!( + target: "xcm::pallet_xcm::wrap_version", + "Could not determine a version to wrap XCM for destination: {:?}", + dest, + ); + () + }) + .and_then(|v| xcm.into().into_version(v.min(XCM_VERSION))) + } +} + +impl VersionChangeNotifier for Pallet { + /// Start notifying `location` should the XCM version of this chain change. + /// + /// When it does, this type should ensure a `QueryResponse` message is sent with the given + /// `query_id` & `max_weight` and with a `response` of `Response::Version`. This should happen + /// until/unless `stop` is called with the correct `query_id`. + /// + /// If the `location` has an ongoing notification and when this function is called, then an + /// error should be returned. + fn start( + dest: &MultiLocation, + query_id: QueryId, + max_weight: Weight, + _context: &XcmContext, + ) -> XcmResult { + let versioned_dest = LatestVersionedMultiLocation(dest); + let already = VersionNotifyTargets::::contains_key(XCM_VERSION, versioned_dest); + ensure!(!already, XcmError::InvalidLocation); + + let xcm_version = T::AdvertisedXcmVersion::get(); + let response = Response::Version(xcm_version); + let instruction = QueryResponse { query_id, response, max_weight, querier: None }; + let (_hash, cost) = send_xcm::(*dest, Xcm(vec![instruction]))?; + Self::deposit_event(Event::::VersionNotifyStarted(*dest, cost)); + + let value = (query_id, max_weight, xcm_version); + VersionNotifyTargets::::insert(XCM_VERSION, versioned_dest, value); + Ok(()) + } + + /// Stop notifying `location` should the XCM change. This is a no-op if there was never a + /// subscription. + fn stop(dest: &MultiLocation, _context: &XcmContext) -> XcmResult { + VersionNotifyTargets::::remove(XCM_VERSION, LatestVersionedMultiLocation(dest)); + Ok(()) + } + + /// Return true if a location is subscribed to XCM version changes. + fn is_subscribed(dest: &MultiLocation) -> bool { + let versioned_dest = LatestVersionedMultiLocation(dest); + VersionNotifyTargets::::contains_key(XCM_VERSION, versioned_dest) + } +} + +impl DropAssets for Pallet { + fn drop_assets(origin: &MultiLocation, assets: Assets, _context: &XcmContext) -> Weight { + if assets.is_empty() { + return Weight::zero() } + let versioned = VersionedMultiAssets::from(MultiAssets::from(assets)); + let hash = BlakeTwo256::hash_of(&(&origin, &versioned)); + AssetTraps::::mutate(hash, |n| *n += 1); + Self::deposit_event(Event::AssetsTrapped(hash, *origin, versioned)); + // TODO #3735: Put the real weight in there. + Weight::zero() } +} - impl ClaimAssets for Pallet { - fn claim_assets( - origin: &MultiLocation, - ticket: &MultiLocation, - assets: &MultiAssets, - ) -> bool { - let mut versioned = VersionedMultiAssets::from(assets.clone()); - match (ticket.parents, &ticket.interior) { - (0, X1(GeneralIndex(i))) => - versioned = match versioned.into_version(*i as u32) { - Ok(v) => v, - Err(()) => return false, - }, - (0, Here) => (), - _ => return false, - }; - let hash = BlakeTwo256::hash_of(&(origin, versioned.clone())); - match AssetTraps::::get(hash) { - 0 => return false, - 1 => AssetTraps::::remove(hash), - n => AssetTraps::::insert(hash, n - 1), - } - Self::deposit_event(Event::AssetsClaimed(hash, origin.clone(), versioned)); - return true +impl ClaimAssets for Pallet { + fn claim_assets( + origin: &MultiLocation, + ticket: &MultiLocation, + assets: &MultiAssets, + _context: &XcmContext, + ) -> bool { + let mut versioned = VersionedMultiAssets::from(assets.clone()); + match (ticket.parents, &ticket.interior) { + (0, X1(GeneralIndex(i))) => + versioned = match versioned.into_version(*i as u32) { + Ok(v) => v, + Err(()) => return false, + }, + (0, Here) => (), + _ => return false, + }; + let hash = BlakeTwo256::hash_of(&(origin, versioned.clone())); + match AssetTraps::::get(hash) { + 0 => return false, + 1 => AssetTraps::::remove(hash), + n => AssetTraps::::insert(hash, n - 1), } + Self::deposit_event(Event::AssetsClaimed(hash, *origin, versioned)); + return true } +} - impl OnResponse for Pallet { - fn expecting_response(origin: &MultiLocation, query_id: QueryId) -> bool { - match Queries::::get(query_id) { - Some(QueryStatus::Pending { responder, .. }) => - MultiLocation::try_from(responder).map_or(false, |r| origin == &r), - Some(QueryStatus::VersionNotifier { origin: r, .. }) => - MultiLocation::try_from(r).map_or(false, |r| origin == &r), - _ => false, - } +impl OnResponse for Pallet { + fn expecting_response( + origin: &MultiLocation, + query_id: QueryId, + querier: Option<&MultiLocation>, + ) -> bool { + match Queries::::get(query_id) { + Some(QueryStatus::Pending { responder, maybe_match_querier, .. }) => + MultiLocation::try_from(responder).map_or(false, |r| origin == &r) && + maybe_match_querier.map_or(true, |match_querier| { + MultiLocation::try_from(match_querier).map_or(false, |match_querier| { + querier.map_or(false, |q| q == &match_querier) + }) + }), + Some(QueryStatus::VersionNotifier { origin: r, .. }) => + MultiLocation::try_from(r).map_or(false, |r| origin == &r), + _ => false, } + } - fn on_response( - origin: &MultiLocation, - query_id: QueryId, - response: Response, - max_weight: u64, - ) -> u64 { - match (response, Queries::::get(query_id)) { - ( - Response::Version(v), - Some(QueryStatus::VersionNotifier { origin: expected_origin, is_active }), - ) => { - let origin: MultiLocation = match expected_origin.try_into() { - Ok(o) if &o == origin => o, - Ok(o) => { - Self::deposit_event(Event::InvalidResponder( - origin.clone(), - query_id, - Some(o), - )); - return 0 - }, - _ => { - Self::deposit_event(Event::InvalidResponder( - origin.clone(), - query_id, - None, - )); - // TODO #3735: Correct weight for this. - return 0 - }, - }; - // TODO #3735: Check max_weight is correct. - if !is_active { - Queries::::insert( - query_id, - QueryStatus::VersionNotifier { - origin: origin.clone().into(), - is_active: true, - }, - ); - } - // We're being notified of a version change. - SupportedVersion::::insert( - XCM_VERSION, - LatestVersionedMultiLocation(&origin), - v, + fn on_response( + origin: &MultiLocation, + query_id: QueryId, + querier: Option<&MultiLocation>, + response: Response, + max_weight: Weight, + _context: &XcmContext, + ) -> Weight { + match (response, Queries::::get(query_id)) { + ( + Response::Version(v), + Some(QueryStatus::VersionNotifier { origin: expected_origin, is_active }), + ) => { + let origin: MultiLocation = match expected_origin.try_into() { + Ok(o) if &o == origin => o, + Ok(o) => { + Self::deposit_event(Event::InvalidResponder(*origin, query_id, Some(o))); + return Weight::zero() + }, + _ => { + Self::deposit_event(Event::InvalidResponder(*origin, query_id, None)); + // TODO #3735: Correct weight for this. + return Weight::zero() + }, + }; + // TODO #3735: Check max_weight is correct. + if !is_active { + Queries::::insert( + query_id, + QueryStatus::VersionNotifier { origin: origin.into(), is_active: true }, ); - Self::deposit_event(Event::SupportedVersionChanged(origin, v)); - 0 - }, - (response, Some(QueryStatus::Pending { responder, maybe_notify, .. })) => { - let responder = match MultiLocation::try_from(responder) { - Ok(r) => r, + } + // We're being notified of a version change. + SupportedVersion::::insert( + XCM_VERSION, + LatestVersionedMultiLocation(&origin), + v, + ); + Self::deposit_event(Event::SupportedVersionChanged(origin, v)); + Weight::zero() + }, + ( + response, + Some(QueryStatus::Pending { responder, maybe_notify, maybe_match_querier, .. }), + ) => { + if let Some(match_querier) = maybe_match_querier { + let match_querier = match MultiLocation::try_from(match_querier) { + Ok(mq) => mq, Err(_) => { - Self::deposit_event(Event::InvalidResponderVersion( - origin.clone(), - query_id, - )); - return 0 + Self::deposit_event(Event::InvalidQuerierVersion(*origin, query_id)); + return Weight::zero() }, }; - if origin != &responder { - Self::deposit_event(Event::InvalidResponder( - origin.clone(), + if querier.map_or(true, |q| q != &match_querier) { + Self::deposit_event(Event::InvalidQuerier( + *origin, query_id, - Some(responder), + match_querier, + querier.cloned(), )); - return 0 + return Weight::zero() } - return match maybe_notify { - Some((pallet_index, call_index)) => { - // This is a bit horrible, but we happen to know that the `Call` will - // be built by `(pallet_index: u8, call_index: u8, QueryId, Response)`. - // So we just encode that and then re-encode to a real Call. - let bare = (pallet_index, call_index, query_id, response); - if let Ok(call) = bare.using_encoded(|mut bytes| { - ::RuntimeCall::decode(&mut bytes) - }) { - Queries::::remove(query_id); - let weight = call.get_dispatch_info().weight; - let max_weight = Weight::from_ref_time(max_weight); - if weight.any_gt(max_weight) { - let e = Event::NotifyOverweight( + } + let responder = match MultiLocation::try_from(responder) { + Ok(r) => r, + Err(_) => { + Self::deposit_event(Event::InvalidResponderVersion(*origin, query_id)); + return Weight::zero() + }, + }; + if origin != &responder { + Self::deposit_event(Event::InvalidResponder( + *origin, + query_id, + Some(responder), + )); + return Weight::zero() + } + return match maybe_notify { + Some((pallet_index, call_index)) => { + // This is a bit horrible, but we happen to know that the `Call` will + // be built by `(pallet_index: u8, call_index: u8, QueryId, Response)`. + // So we just encode that and then re-encode to a real Call. + let bare = (pallet_index, call_index, query_id, response); + if let Ok(call) = bare.using_encoded(|mut bytes| { + ::RuntimeCall::decode(&mut bytes) + }) { + Queries::::remove(query_id); + let weight = call.get_dispatch_info().weight; + if weight.any_gt(max_weight) { + let e = Event::NotifyOverweight( + query_id, + pallet_index, + call_index, + weight, + max_weight, + ); + Self::deposit_event(e); + return Weight::zero() + } + let dispatch_origin = Origin::Response(*origin).into(); + match call.dispatch(dispatch_origin) { + Ok(post_info) => { + let e = Event::Notified(query_id, pallet_index, call_index); + Self::deposit_event(e); + post_info.actual_weight + }, + Err(error_and_info) => { + let e = Event::NotifyDispatchError( query_id, pallet_index, call_index, - weight, - max_weight, ); Self::deposit_event(e); - return 0 - } - let dispatch_origin = Origin::Response(origin.clone()).into(); - match call.dispatch(dispatch_origin) { - Ok(post_info) => { - let e = Event::Notified(query_id, pallet_index, call_index); - Self::deposit_event(e); - post_info.actual_weight - }, - Err(error_and_info) => { - let e = Event::NotifyDispatchError( - query_id, - pallet_index, - call_index, - ); - Self::deposit_event(e); - // Not much to do with the result as it is. It's up to the parachain to ensure that the - // message makes sense. - error_and_info.post_info.actual_weight - }, - } - .unwrap_or(weight) - .ref_time() - } else { - let e = - Event::NotifyDecodeFailed(query_id, pallet_index, call_index); - Self::deposit_event(e); - 0 + // Not much to do with the result as it is. It's up to the parachain to ensure that the + // message makes sense. + error_and_info.post_info.actual_weight + }, } - }, - None => { - let e = Event::ResponseReady(query_id, response.clone()); + .unwrap_or(weight) + } else { + let e = Event::NotifyDecodeFailed(query_id, pallet_index, call_index); Self::deposit_event(e); - let at = frame_system::Pallet::::current_block_number(); - let response = response.into(); - Queries::::insert(query_id, QueryStatus::Ready { response, at }); - 0 - }, - } - }, - _ => { - Self::deposit_event(Event::UnexpectedResponse(origin.clone(), query_id)); - return 0 - }, - } + Weight::zero() + } + }, + None => { + let e = Event::ResponseReady(query_id, response.clone()); + Self::deposit_event(e); + let at = frame_system::Pallet::::current_block_number(); + let response = response.into(); + Queries::::insert(query_id, QueryStatus::Ready { response, at }); + Weight::zero() + }, + } + }, + _ => { + Self::deposit_event(Event::UnexpectedResponse(*origin, query_id)); + Weight::zero() + }, } } } diff --git a/xcm/pallet-xcm/src/migration.rs b/xcm/pallet-xcm/src/migration.rs new file mode 100644 index 000000000000..c7e8ca786244 --- /dev/null +++ b/xcm/pallet-xcm/src/migration.rs @@ -0,0 +1,63 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use crate::{Config, Pallet, Store}; +use frame_support::{ + pallet_prelude::*, + traits::{OnRuntimeUpgrade, StorageVersion}, + weights::Weight, +}; + +pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + +const DEFAULT_PROOF_SIZE: u64 = 64 * 1024; + +pub mod v1 { + use super::*; + + pub struct MigrateToV1(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV1 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + ensure!(StorageVersion::get::>() == 0, "must upgrade linearly"); + + Ok(sp_std::vec::Vec::new()) + } + + fn on_runtime_upgrade() -> Weight { + if StorageVersion::get::>() == 0 { + let mut weight = T::DbWeight::get().reads(1); + + let translate = |pre: (u64, u64, u32)| -> Option<(u64, Weight, u32)> { + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + let translated = (pre.0, Weight::from_parts(pre.1, DEFAULT_PROOF_SIZE), pre.2); + log::info!("Migrated VersionNotifyTarget {:?} to {:?}", pre, translated); + Some(translated) + }; + + as Store>::VersionNotifyTargets::translate_values(translate); + + log::info!("v1 applied successfully"); + STORAGE_VERSION.put::>(); + + weight.saturating_add(T::DbWeight::get().writes(1)) + } else { + log::warn!("skipping v1, should be removed"); + T::DbWeight::get().reads(1) + } + } + } +} diff --git a/xcm/pallet-xcm/src/mock.rs b/xcm/pallet-xcm/src/mock.rs index 10a04034862b..3c9184898f63 100644 --- a/xcm/pallet-xcm/src/mock.rs +++ b/xcm/pallet-xcm/src/mock.rs @@ -14,23 +14,28 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use frame_support::{construct_runtime, parameter_types, traits::Everything}; +use codec::Encode; +use frame_support::{ + construct_runtime, parameter_types, + traits::{Everything, Nothing}, + weights::Weight, +}; use polkadot_parachain::primitives::Id as ParaId; use polkadot_runtime_parachains::origin; use sp_core::H256; use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; pub use sp_std::{cell::RefCell, fmt::Debug, marker::PhantomData}; -use xcm::latest::prelude::*; +use xcm::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, Case, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, - FixedWeightBounds, IsConcrete, LocationInverter, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + FixedWeightBounds, IsConcrete, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, }; use xcm_executor::XcmExecutor; -use crate as pallet_xcm; +use crate::{self as pallet_xcm, TestWeightInfo}; pub type AccountId = AccountId32; pub type Balance = u128; @@ -74,23 +79,27 @@ pub mod pallet_test_notifier { #[pallet::call] impl Pallet { #[pallet::call_index(0)] - #[pallet::weight(1_000_000)] - pub fn prepare_new_query(origin: OriginFor) -> DispatchResult { + #[pallet::weight(Weight::from_parts(1_000_000, 1_000_000))] + pub fn prepare_new_query(origin: OriginFor, querier: MultiLocation) -> DispatchResult { let who = ensure_signed(origin)?; let id = who .using_encoded(|mut d| <[u8; 32]>::decode(&mut d)) .map_err(|_| Error::::BadAccountFormat)?; let qid = crate::Pallet::::new_query( - Junction::AccountId32 { network: Any, id }.into(), + Junction::AccountId32 { network: None, id }, 100u32.into(), + querier, ); Self::deposit_event(Event::::QueryPrepared(qid)); Ok(()) } #[pallet::call_index(1)] - #[pallet::weight(1_000_000)] - pub fn prepare_new_notify_query(origin: OriginFor) -> DispatchResult { + #[pallet::weight(Weight::from_parts(1_000_000, 1_000_000))] + pub fn prepare_new_notify_query( + origin: OriginFor, + querier: MultiLocation, + ) -> DispatchResult { let who = ensure_signed(origin)?; let id = who .using_encoded(|mut d| <[u8; 32]>::decode(&mut d)) @@ -98,16 +107,17 @@ pub mod pallet_test_notifier { let call = Call::::notification_received { query_id: 0, response: Default::default() }; let qid = crate::Pallet::::new_notify_query( - Junction::AccountId32 { network: Any, id }.into(), + Junction::AccountId32 { network: None, id }, ::RuntimeCall::from(call), 100u32.into(), + querier, ); Self::deposit_event(Event::::NotifyQueryPrepared(qid)); Ok(()) } #[pallet::call_index(2)] - #[pallet::weight(1_000_000)] + #[pallet::weight(Weight::from_parts(1_000_000, 1_000_000))] pub fn notification_received( origin: OriginFor, query_id: QueryId, @@ -150,23 +160,40 @@ pub(crate) fn take_sent_xcm() -> Vec<(MultiLocation, Xcm<()>)> { /// Sender that never returns error, always sends pub struct TestSendXcm; impl SendXcm for TestSendXcm { - fn send_xcm(dest: impl Into, msg: Xcm<()>) -> SendResult { - SENT_XCM.with(|q| q.borrow_mut().push((dest.into(), msg))); - Ok(()) + type Ticket = (MultiLocation, Xcm<()>); + fn validate( + dest: &mut Option, + msg: &mut Option>, + ) -> SendResult<(MultiLocation, Xcm<()>)> { + let pair = (dest.take().unwrap(), msg.take().unwrap()); + Ok((pair, MultiAssets::new())) + } + fn deliver(pair: (MultiLocation, Xcm<()>)) -> Result { + let hash = fake_message_hash(&pair.1); + SENT_XCM.with(|q| q.borrow_mut().push(pair)); + Ok(hash) } } /// Sender that returns error if `X8` junction and stops routing pub struct TestSendXcmErrX8; impl SendXcm for TestSendXcmErrX8 { - fn send_xcm(dest: impl Into, msg: Xcm<()>) -> SendResult { - let dest = dest.into(); + type Ticket = (MultiLocation, Xcm<()>); + fn validate( + dest: &mut Option, + msg: &mut Option>, + ) -> SendResult<(MultiLocation, Xcm<()>)> { + let (dest, msg) = (dest.take().unwrap(), msg.take().unwrap()); if dest.len() == 8 { Err(SendError::Transport("Destination location full")) } else { - SENT_XCM.with(|q| q.borrow_mut().push((dest, msg))); - Ok(()) + Ok(((dest, msg), MultiAssets::new())) } } + fn deliver(pair: (MultiLocation, Xcm<()>)) -> Result { + let hash = fake_message_hash(&pair.1); + SENT_XCM.with(|q| q.borrow_mut().push(pair)); + Ok(hash) + } } parameter_types! { @@ -219,9 +246,9 @@ impl pallet_balances::Config for Test { } parameter_types! { - pub const RelayLocation: MultiLocation = Here.into(); - pub const AnyNetwork: NetworkId = NetworkId::Any; - pub Ancestry: MultiLocation = Here.into(); + pub const RelayLocation: MultiLocation = Here.into_location(); + pub const AnyNetwork: Option = None; + pub UniversalLocation: InteriorMultiLocation = Here; pub UnitWeightCost: u64 = 1_000; } @@ -239,10 +266,11 @@ type LocalOriginConverter = ( ); parameter_types! { - pub const BaseXcmWeight: u64 = 1_000; - pub CurrencyPerSecond: (AssetId, u128) = (Concrete(RelayLocation::get()), 1); + pub const BaseXcmWeight: Weight = Weight::from_parts(1_000, 1_000); + pub CurrencyPerSecondPerByte: (AssetId, u128, u128) = (Concrete(RelayLocation::get()), 1, 1); pub TrustedAssets: (MultiAssetFilter, MultiLocation) = (All.into(), Here.into()); pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } pub type Barrier = ( @@ -260,20 +288,34 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = LocalOriginConverter; type IsReserve = (); type IsTeleporter = Case; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; - type Trader = FixedRateOfFungible; + type Trader = FixedRateOfFungible; type ResponseHandler = XcmPallet; type AssetTrap = XcmPallet; + type AssetLocker = (); + type AssetExchanger = (); type AssetClaims = XcmPallet; type SubscriptionService = XcmPallet; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; } pub type LocalOriginToLocation = SignedToAccountId32; parameter_types! { - pub static AdvertisedXcmVersion: pallet_xcm::XcmVersion = 2; + pub static AdvertisedXcmVersion: pallet_xcm::XcmVersion = 3; +} + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Parachain(1000).into()); } impl pallet_xcm::Config for Test { @@ -286,11 +328,19 @@ impl pallet_xcm::Config for Test { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = AdvertisedXcmVersion; + type TrustedLockers = (); + type SovereignAccountOf = AccountId32Aliases<(), AccountId32>; + type Currency = Balances; + type CurrencyMatcher = IsConcrete; + type MaxLockers = frame_support::traits::ConstU32<8>; + type WeightInfo = TestWeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; } impl origin::Config for Test {} @@ -314,7 +364,10 @@ pub(crate) fn buy_execution(fees: impl Into) -> Instruction { BuyExecution { fees: fees.into(), weight_limit: Unlimited } } -pub(crate) fn buy_limited_execution(fees: impl Into, weight: u64) -> Instruction { +pub(crate) fn buy_limited_execution( + fees: impl Into, + weight: Weight, +) -> Instruction { use xcm::latest::prelude::*; BuyExecution { fees: fees.into(), weight_limit: Limited(weight) } } @@ -338,3 +391,7 @@ pub(crate) fn new_test_ext_with_balances( ext.execute_with(|| System::set_block_number(1)); ext } + +pub(crate) fn fake_message_hash(message: &Xcm) -> XcmHash { + message.using_encoded(sp_io::hashing::blake2_256) +} diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index 6da02cf10cf6..2700124976f1 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -25,7 +25,7 @@ use frame_support::{ }; use polkadot_parachain::primitives::Id as ParaId; use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, Hash}; -use xcm::prelude::*; +use xcm::{latest::QueryResponseInfo, prelude::*}; use xcm_builder::AllowKnownQueryResponses; use xcm_executor::{traits::ShouldExecute, XcmExecutor}; @@ -41,7 +41,7 @@ fn report_outcome_notify_works() { (ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; - let sender = AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into(); + let sender: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); let mut message = Xcm(vec![TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone(), @@ -52,36 +52,47 @@ fn report_outcome_notify_works() { }; let notify = RuntimeCall::TestNotifier(call); new_test_ext_with_balances(balances).execute_with(|| { - XcmPallet::report_outcome_notify(&mut message, Parachain(PARA_ID).into(), notify, 100) - .unwrap(); + XcmPallet::report_outcome_notify( + &mut message, + Parachain(PARA_ID).into_location(), + notify, + 100, + ) + .unwrap(); assert_eq!( message, Xcm(vec![ - SetAppendix(Xcm(vec![ReportError { + SetAppendix(Xcm(vec![ReportError(QueryResponseInfo { + destination: Parent.into(), query_id: 0, - dest: Parent.into(), - max_response_weight: 1_000_000 - },])), + max_weight: Weight::from_parts(1_000_000, 1_000_000), + })])), TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, ]) ); + let querier: MultiLocation = Here.into(); let status = QueryStatus::Pending { responder: MultiLocation::from(Parachain(PARA_ID)).into(), maybe_notify: Some((4, 2)), timeout: 100, + maybe_match_querier: Some(querier.clone().into()), }; assert_eq!(crate::Queries::::iter().collect::>(), vec![(0, status)]); + let message = Xcm(vec![QueryResponse { + query_id: 0, + response: Response::ExecutionResult(None), + max_weight: Weight::from_parts(1_000_000, 1_000_000), + querier: Some(querier), + }]); + let hash = fake_message_hash(&message); let r = XcmExecutor::::execute_xcm( - Parachain(PARA_ID).into(), - Xcm(vec![QueryResponse { - query_id: 0, - response: Response::ExecutionResult(None), - max_weight: 1_000_000, - }]), - 1_000_000_000, + Parachain(PARA_ID), + message, + hash, + Weight::from_parts(1_000_000_000, 1_000_000_000), ); - assert_eq!(r, Outcome::Complete(1_000)); + assert_eq!(r, Outcome::Complete(Weight::from_parts(1_000, 1_000))); assert_eq!( last_events(2), vec![ @@ -103,41 +114,147 @@ fn report_outcome_works() { (ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; - let sender = AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into(); + let sender: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); let mut message = Xcm(vec![TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone(), }]); new_test_ext_with_balances(balances).execute_with(|| { - XcmPallet::report_outcome(&mut message, Parachain(PARA_ID).into(), 100).unwrap(); + XcmPallet::report_outcome(&mut message, Parachain(PARA_ID).into_location(), 100).unwrap(); assert_eq!( message, Xcm(vec![ - SetAppendix(Xcm(vec![ReportError { + SetAppendix(Xcm(vec![ReportError(QueryResponseInfo { + destination: Parent.into(), query_id: 0, - dest: Parent.into(), - max_response_weight: 0 - },])), + max_weight: Weight::zero(), + })])), TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, ]) ); + let querier: MultiLocation = Here.into(); let status = QueryStatus::Pending { responder: MultiLocation::from(Parachain(PARA_ID)).into(), maybe_notify: None, timeout: 100, + maybe_match_querier: Some(querier.clone().into()), }; assert_eq!(crate::Queries::::iter().collect::>(), vec![(0, status)]); + let message = Xcm(vec![QueryResponse { + query_id: 0, + response: Response::ExecutionResult(None), + max_weight: Weight::zero(), + querier: Some(querier), + }]); + let hash = fake_message_hash(&message); let r = XcmExecutor::::execute_xcm( - Parachain(PARA_ID).into(), - Xcm(vec![QueryResponse { - query_id: 0, - response: Response::ExecutionResult(None), - max_weight: 0, - }]), - 1_000_000_000, + Parachain(PARA_ID), + message, + hash, + Weight::from_parts(1_000_000_000, 1_000_000_000), ); - assert_eq!(r, Outcome::Complete(1_000)); + assert_eq!(r, Outcome::Complete(Weight::from_parts(1_000, 1_000))); + assert_eq!( + last_event(), + RuntimeEvent::XcmPallet(crate::Event::ResponseReady( + 0, + Response::ExecutionResult(None), + )) + ); + + let response = Some((Response::ExecutionResult(None), 1)); + assert_eq!(XcmPallet::take_response(0), response); + }); +} + +#[test] +fn custom_querier_works() { + let balances = vec![ + (ALICE, INITIAL_BALANCE), + (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), + ]; + new_test_ext_with_balances(balances).execute_with(|| { + let querier: MultiLocation = + (Parent, AccountId32 { network: None, id: ALICE.into() }).into(); + + let r = TestNotifier::prepare_new_query(RuntimeOrigin::signed(ALICE), querier.clone()); + assert_eq!(r, Ok(())); + let status = QueryStatus::Pending { + responder: MultiLocation::from(AccountId32 { network: None, id: ALICE.into() }).into(), + maybe_notify: None, + timeout: 100, + maybe_match_querier: Some(querier.clone().into()), + }; + assert_eq!(crate::Queries::::iter().collect::>(), vec![(0, status)]); + + // Supplying no querier when one is expected will fail + let message = Xcm(vec![QueryResponse { + query_id: 0, + response: Response::ExecutionResult(None), + max_weight: Weight::zero(), + querier: None, + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm_in_credit( + AccountId32 { network: None, id: ALICE.into() }, + message, + hash, + Weight::from_parts(1_000_000_000, 1_000_000_000), + Weight::from_parts(1_000, 1_000), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(1_000, 1_000))); + assert_eq!( + last_event(), + RuntimeEvent::XcmPallet(crate::Event::InvalidQuerier( + AccountId32 { network: None, id: ALICE.into() }.into(), + 0, + querier.clone(), + None, + )), + ); + + // Supplying the wrong querier will also fail + let message = Xcm(vec![QueryResponse { + query_id: 0, + response: Response::ExecutionResult(None), + max_weight: Weight::zero(), + querier: Some(MultiLocation::here()), + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm_in_credit( + AccountId32 { network: None, id: ALICE.into() }, + message, + hash, + Weight::from_parts(1_000_000_000, 1_000_000_000), + Weight::from_parts(1_000, 1_000), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(1_000, 1_000))); + assert_eq!( + last_event(), + RuntimeEvent::XcmPallet(crate::Event::InvalidQuerier( + AccountId32 { network: None, id: ALICE.into() }.into(), + 0, + querier.clone(), + Some(MultiLocation::here()), + )), + ); + + // Multiple failures should not have changed the query state + let message = Xcm(vec![QueryResponse { + query_id: 0, + response: Response::ExecutionResult(None), + max_weight: Weight::zero(), + querier: Some(querier), + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + AccountId32 { network: None, id: ALICE.into() }, + message, + hash, + Weight::from_parts(1_000_000_000, 1_000_000_000), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(1_000, 1_000))); assert_eq!( last_event(), RuntimeEvent::XcmPallet(crate::Event::ResponseReady( @@ -161,13 +278,12 @@ fn send_works() { (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; new_test_ext_with_balances(balances).execute_with(|| { - let sender: MultiLocation = - AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into(); + let sender: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); let message = Xcm(vec![ ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), ClearOrigin, buy_execution((Parent, SEND_AMOUNT)), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: sender.clone() }, ]); let versioned_dest = Box::new(RelayLocation::get().into()); let versioned_message = Box::new(VersionedXcm::from(message.clone())); @@ -205,11 +321,11 @@ fn send_fails_when_xcm_router_blocks() { ]; new_test_ext_with_balances(balances).execute_with(|| { let sender: MultiLocation = - Junction::AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into(); + Junction::AccountId32 { network: None, id: ALICE.into() }.into(); let message = Xcm(vec![ ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), buy_execution((Parent, SEND_AMOUNT)), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: sender.clone() }, ]); assert_noop!( XcmPallet::send( @@ -233,9 +349,9 @@ fn teleport_assets_works() { (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; new_test_ext_with_balances(balances).execute_with(|| { - let weight = 2 * BaseXcmWeight::get(); + let weight = BaseXcmWeight::get() * 2; assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); - let dest: MultiLocation = AccountId32 { network: Any, id: BOB.into() }.into(); + let dest: MultiLocation = AccountId32 { network: None, id: BOB.into() }.into(); assert_ok!(XcmPallet::teleport_assets( RuntimeOrigin::signed(ALICE), Box::new(RelayLocation::get().into()), @@ -251,13 +367,13 @@ fn teleport_assets_works() { Xcm(vec![ ReceiveTeleportedAsset((Here, SEND_AMOUNT).into()), ClearOrigin, - buy_limited_execution((Here, SEND_AMOUNT), 4000), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, + buy_limited_execution((Here, SEND_AMOUNT), Weight::from_parts(4000, 4000)), + DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, ]), )] ); let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1); - let _check_v0_ok: xcm::v0::Xcm<()> = versioned_sent.try_into().unwrap(); + let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap(); assert_eq!( last_event(), RuntimeEvent::XcmPallet(crate::Event::Attempted(Outcome::Complete(weight))) @@ -276,16 +392,16 @@ fn limited_teleport_assets_works() { (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; new_test_ext_with_balances(balances).execute_with(|| { - let weight = 2 * BaseXcmWeight::get(); + let weight = BaseXcmWeight::get() * 2; assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); - let dest: MultiLocation = AccountId32 { network: Any, id: BOB.into() }.into(); + let dest: MultiLocation = AccountId32 { network: None, id: BOB.into() }.into(); assert_ok!(XcmPallet::limited_teleport_assets( RuntimeOrigin::signed(ALICE), Box::new(RelayLocation::get().into()), Box::new(dest.clone().into()), Box::new((Here, SEND_AMOUNT).into()), 0, - WeightLimit::Limited(5000), + WeightLimit::Limited(Weight::from_parts(5000, 5000)), )); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT); assert_eq!( @@ -295,13 +411,13 @@ fn limited_teleport_assets_works() { Xcm(vec![ ReceiveTeleportedAsset((Here, SEND_AMOUNT).into()), ClearOrigin, - buy_limited_execution((Here, SEND_AMOUNT), 5000), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, + buy_limited_execution((Here, SEND_AMOUNT), Weight::from_parts(5000, 5000)), + DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, ]), )] ); let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1); - let _check_v0_ok: xcm::v0::Xcm<()> = versioned_sent.try_into().unwrap(); + let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap(); assert_eq!( last_event(), RuntimeEvent::XcmPallet(crate::Event::Attempted(Outcome::Complete(weight))) @@ -320,9 +436,9 @@ fn unlimited_teleport_assets_works() { (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; new_test_ext_with_balances(balances).execute_with(|| { - let weight = 2 * BaseXcmWeight::get(); + let weight = BaseXcmWeight::get() * 2; assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); - let dest: MultiLocation = AccountId32 { network: Any, id: BOB.into() }.into(); + let dest: MultiLocation = AccountId32 { network: None, id: BOB.into() }.into(); assert_ok!(XcmPallet::limited_teleport_assets( RuntimeOrigin::signed(ALICE), Box::new(RelayLocation::get().into()), @@ -340,7 +456,7 @@ fn unlimited_teleport_assets_works() { ReceiveTeleportedAsset((Here, SEND_AMOUNT).into()), ClearOrigin, buy_execution((Here, SEND_AMOUNT)), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, ]), )] ); @@ -363,12 +479,11 @@ fn reserve_transfer_assets_works() { ]; new_test_ext_with_balances(balances).execute_with(|| { let weight = BaseXcmWeight::get(); - let dest: MultiLocation = - Junction::AccountId32 { network: NetworkId::Any, id: ALICE.into() }.into(); + let dest: MultiLocation = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); assert_ok!(XcmPallet::reserve_transfer_assets( RuntimeOrigin::signed(ALICE), - Box::new(Parachain(PARA_ID).into().into()), + Box::new(Parachain(PARA_ID).into()), Box::new(dest.clone().into()), Box::new((Here, SEND_AMOUNT).into()), 0, @@ -385,13 +500,13 @@ fn reserve_transfer_assets_works() { Xcm(vec![ ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), ClearOrigin, - buy_limited_execution((Parent, SEND_AMOUNT), 4000), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, + buy_limited_execution((Parent, SEND_AMOUNT), Weight::from_parts(4000, 4000)), + DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, ]), )] ); let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1); - let _check_v0_ok: xcm::v0::Xcm<()> = versioned_sent.try_into().unwrap(); + let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap(); assert_eq!( last_event(), RuntimeEvent::XcmPallet(crate::Event::Attempted(Outcome::Complete(weight))) @@ -411,16 +526,15 @@ fn limited_reserve_transfer_assets_works() { ]; new_test_ext_with_balances(balances).execute_with(|| { let weight = BaseXcmWeight::get(); - let dest: MultiLocation = - Junction::AccountId32 { network: NetworkId::Any, id: ALICE.into() }.into(); + let dest: MultiLocation = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); assert_ok!(XcmPallet::limited_reserve_transfer_assets( RuntimeOrigin::signed(ALICE), - Box::new(Parachain(PARA_ID).into().into()), + Box::new(Parachain(PARA_ID).into()), Box::new(dest.clone().into()), Box::new((Here, SEND_AMOUNT).into()), 0, - WeightLimit::Limited(5000), + WeightLimit::Limited(Weight::from_parts(5000, 5000)), )); // Alice spent amount assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - SEND_AMOUNT); @@ -434,13 +548,13 @@ fn limited_reserve_transfer_assets_works() { Xcm(vec![ ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), ClearOrigin, - buy_limited_execution((Parent, SEND_AMOUNT), 5000), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, + buy_limited_execution((Parent, SEND_AMOUNT), Weight::from_parts(5000, 5000)), + DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, ]), )] ); let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1); - let _check_v0_ok: xcm::v0::Xcm<()> = versioned_sent.try_into().unwrap(); + let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap(); assert_eq!( last_event(), RuntimeEvent::XcmPallet(crate::Event::Attempted(Outcome::Complete(weight))) @@ -460,12 +574,11 @@ fn unlimited_reserve_transfer_assets_works() { ]; new_test_ext_with_balances(balances).execute_with(|| { let weight = BaseXcmWeight::get(); - let dest: MultiLocation = - Junction::AccountId32 { network: NetworkId::Any, id: ALICE.into() }.into(); + let dest: MultiLocation = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); assert_ok!(XcmPallet::limited_reserve_transfer_assets( RuntimeOrigin::signed(ALICE), - Box::new(Parachain(PARA_ID).into().into()), + Box::new(Parachain(PARA_ID).into()), Box::new(dest.clone().into()), Box::new((Here, SEND_AMOUNT).into()), 0, @@ -484,7 +597,7 @@ fn unlimited_reserve_transfer_assets_works() { ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), ClearOrigin, buy_execution((Parent, SEND_AMOUNT)), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, ]), )] ); @@ -506,16 +619,15 @@ fn execute_withdraw_to_deposit_works() { (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; new_test_ext_with_balances(balances).execute_with(|| { - let weight = 3 * BaseXcmWeight::get(); - let dest: MultiLocation = - Junction::AccountId32 { network: NetworkId::Any, id: BOB.into() }.into(); + let weight = BaseXcmWeight::get() * 3; + let dest: MultiLocation = Junction::AccountId32 { network: None, id: BOB.into() }.into(); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); assert_ok!(XcmPallet::execute( RuntimeOrigin::signed(ALICE), Box::new(VersionedXcm::from(Xcm(vec![ WithdrawAsset((Here, SEND_AMOUNT).into()), buy_execution((Here, SEND_AMOUNT)), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, ]))), weight )); @@ -533,9 +645,8 @@ fn execute_withdraw_to_deposit_works() { fn trapped_assets_can_be_claimed() { let balances = vec![(ALICE, INITIAL_BALANCE), (BOB, INITIAL_BALANCE)]; new_test_ext_with_balances(balances).execute_with(|| { - let weight = 6 * BaseXcmWeight::get(); - let dest: MultiLocation = - Junction::AccountId32 { network: NetworkId::Any, id: BOB.into() }.into(); + let weight = BaseXcmWeight::get() * 6; + let dest: MultiLocation = Junction::AccountId32 { network: None, id: BOB.into() }.into(); assert_ok!(XcmPallet::execute( RuntimeOrigin::signed(ALICE), @@ -547,12 +658,12 @@ fn trapped_assets_can_be_claimed() { // This will make an error. Trap(0), // This would succeed, but we never get to it. - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest.clone() }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: dest.clone() }, ]))), weight )); let source: MultiLocation = - Junction::AccountId32 { network: NetworkId::Any, id: ALICE.into() }.into(); + Junction::AccountId32 { network: None, id: ALICE.into() }.into(); let trapped = AssetTraps::::iter().collect::>(); let vma = VersionedMultiAssets::from(MultiAssets::from((Here, SEND_AMOUNT))); let hash = BlakeTwo256::hash_of(&(source.clone(), vma.clone())); @@ -561,7 +672,7 @@ fn trapped_assets_can_be_claimed() { vec![ RuntimeEvent::XcmPallet(crate::Event::AssetsTrapped(hash.clone(), source, vma)), RuntimeEvent::XcmPallet(crate::Event::Attempted(Outcome::Complete( - 5 * BaseXcmWeight::get() + BaseXcmWeight::get() * 5 ))) ] ); @@ -571,13 +682,13 @@ fn trapped_assets_can_be_claimed() { let expected = vec![(hash, 1u32)]; assert_eq!(trapped, expected); - let weight = 3 * BaseXcmWeight::get(); + let weight = BaseXcmWeight::get() * 3; assert_ok!(XcmPallet::execute( RuntimeOrigin::signed(ALICE), Box::new(VersionedXcm::from(Xcm(vec![ ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() }, buy_execution((Here, SEND_AMOUNT)), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest.clone() }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: dest.clone() }, ]))), weight )); @@ -586,13 +697,13 @@ fn trapped_assets_can_be_claimed() { assert_eq!(Balances::total_balance(&BOB), INITIAL_BALANCE + SEND_AMOUNT); assert_eq!(AssetTraps::::iter().collect::>(), vec![]); - let weight = 3 * BaseXcmWeight::get(); + let weight = BaseXcmWeight::get() * 3; assert_ok!(XcmPallet::execute( RuntimeOrigin::signed(ALICE), Box::new(VersionedXcm::from(Xcm(vec![ ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() }, buy_execution((Here, SEND_AMOUNT)), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, ]))), weight )); @@ -609,15 +720,15 @@ fn trapped_assets_can_be_claimed() { #[test] fn fake_latest_versioned_multilocation_works() { use codec::Encode; - let remote = Parachain(1000).into(); + let remote: MultiLocation = Parachain(1000).into(); let versioned_remote = LatestVersionedMultiLocation(&remote); - assert_eq!(versioned_remote.encode(), VersionedMultiLocation::from(remote.clone()).encode()); + assert_eq!(versioned_remote.encode(), remote.into_versioned().encode()); } #[test] fn basic_subscription_works() { new_test_ext_with_balances(vec![]).execute_with(|| { - let remote = Parachain(1000).into(); + let remote: MultiLocation = Parachain(1000).into(); assert_ok!(XcmPallet::force_subscribe_version_notify( RuntimeOrigin::root(), Box::new(remote.clone().into()), @@ -632,27 +743,32 @@ fn basic_subscription_works() { ); assert_eq!( VersionNotifiers::::iter().collect::>(), - vec![(2, remote.clone().into(), 0)] + vec![(XCM_VERSION, remote.clone().into(), 0)] ); assert_eq!( take_sent_xcm(), vec![( remote.clone(), - Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: 0 }]), + Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: Weight::zero() }]), ),] ); let weight = BaseXcmWeight::get(); let mut message = Xcm::<()>(vec![ - // Remote supports XCM v1 - QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(1) }, + // Remote supports XCM v2 + QueryResponse { + query_id: 0, + max_weight: Weight::zero(), + response: Response::Version(1), + querier: None, + }, ]); assert_ok!(AllowKnownQueryResponses::::should_execute( &remote, - &mut message, + message.inner_mut(), weight, - &mut 0 + &mut Weight::zero(), )); }); } @@ -660,13 +776,13 @@ fn basic_subscription_works() { #[test] fn subscriptions_increment_id() { new_test_ext_with_balances(vec![]).execute_with(|| { - let remote = Parachain(1000).into(); + let remote: MultiLocation = Parachain(1000).into(); assert_ok!(XcmPallet::force_subscribe_version_notify( RuntimeOrigin::root(), Box::new(remote.clone().into()), )); - let remote2 = Parachain(1001).into(); + let remote2: MultiLocation = Parachain(1001).into(); assert_ok!(XcmPallet::force_subscribe_version_notify( RuntimeOrigin::root(), Box::new(remote2.clone().into()), @@ -677,11 +793,17 @@ fn subscriptions_increment_id() { vec![ ( remote.clone(), - Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: 0 }]), + Xcm(vec![SubscribeVersion { + query_id: 0, + max_response_weight: Weight::zero() + }]), ), ( remote2.clone(), - Xcm(vec![SubscribeVersion { query_id: 1, max_response_weight: 0 }]), + Xcm(vec![SubscribeVersion { + query_id: 1, + max_response_weight: Weight::zero() + }]), ), ] ); @@ -691,7 +813,7 @@ fn subscriptions_increment_id() { #[test] fn double_subscription_fails() { new_test_ext_with_balances(vec![]).execute_with(|| { - let remote = Parachain(1000).into(); + let remote: MultiLocation = Parachain(1000).into(); assert_ok!(XcmPallet::force_subscribe_version_notify( RuntimeOrigin::root(), Box::new(remote.clone().into()), @@ -709,7 +831,7 @@ fn double_subscription_fails() { #[test] fn unsubscribe_works() { new_test_ext_with_balances(vec![]).execute_with(|| { - let remote = Parachain(1000).into(); + let remote: MultiLocation = Parachain(1000).into(); assert_ok!(XcmPallet::force_subscribe_version_notify( RuntimeOrigin::root(), Box::new(remote.clone().into()), @@ -731,7 +853,10 @@ fn unsubscribe_works() { vec![ ( remote.clone(), - Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: 0 }]), + Xcm(vec![SubscribeVersion { + query_id: 0, + max_response_weight: Weight::zero() + }]), ), (remote.clone(), Xcm(vec![UnsubscribeVersion]),), ] @@ -745,13 +870,20 @@ fn subscription_side_works() { new_test_ext_with_balances(vec![]).execute_with(|| { AdvertisedXcmVersion::set(1); - let remote = Parachain(1000).into(); + let remote: MultiLocation = Parachain(1000).into(); let weight = BaseXcmWeight::get(); - let message = Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: 0 }]); - let r = XcmExecutor::::execute_xcm(remote.clone(), message, weight); + let message = + Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: Weight::zero() }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm(remote.clone(), message, hash, weight); assert_eq!(r, Outcome::Complete(weight)); - let instr = QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(1) }; + let instr = QueryResponse { + query_id: 0, + max_weight: Weight::zero(), + response: Response::Version(1), + querier: None, + }; assert_eq!(take_sent_xcm(), vec![(remote.clone(), Xcm(vec![instr]))]); // A runtime upgrade which doesn't alter the version sends no notifications. @@ -765,7 +897,12 @@ fn subscription_side_works() { // A runtime upgrade which alters the version does send notifications. XcmPallet::on_runtime_upgrade(); XcmPallet::on_initialize(2); - let instr = QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(2) }; + let instr = QueryResponse { + query_id: 0, + max_weight: Weight::zero(), + response: Response::Version(2), + querier: None, + }; assert_eq!(take_sent_xcm(), vec![(remote.clone(), Xcm(vec![instr]))]); }); } @@ -775,25 +912,31 @@ fn subscription_side_upgrades_work_with_notify() { new_test_ext_with_balances(vec![]).execute_with(|| { AdvertisedXcmVersion::set(1); - // An entry from a previous runtime with v0 XCM. - let v0_location = xcm::v0::MultiLocation::X1(xcm::v0::Junction::Parachain(1000)); - let v0_location = VersionedMultiLocation::from(v0_location); - VersionNotifyTargets::::insert(0, v0_location, (69, 0, 1)); - let v1_location = Parachain(1001).into().versioned(); - VersionNotifyTargets::::insert(1, v1_location, (70, 0, 1)); - let v2_location = Parachain(1002).into().versioned(); - VersionNotifyTargets::::insert(2, v2_location, (71, 0, 1)); + // An entry from a previous runtime with v2 XCM. + let v2_location = VersionedMultiLocation::V2(xcm::v2::Junction::Parachain(1001).into()); + VersionNotifyTargets::::insert(1, v2_location, (70, Weight::zero(), 2)); + let v3_location = Parachain(1003).into_versioned(); + VersionNotifyTargets::::insert(3, v3_location, (72, Weight::zero(), 2)); // New version. - AdvertisedXcmVersion::set(2); + AdvertisedXcmVersion::set(3); // A runtime upgrade which alters the version does send notifications. XcmPallet::on_runtime_upgrade(); XcmPallet::on_initialize(1); - let instr0 = QueryResponse { query_id: 69, max_weight: 0, response: Response::Version(2) }; - let instr1 = QueryResponse { query_id: 70, max_weight: 0, response: Response::Version(2) }; - let instr2 = QueryResponse { query_id: 71, max_weight: 0, response: Response::Version(2) }; + let instr1 = QueryResponse { + query_id: 70, + max_weight: Weight::zero(), + response: Response::Version(3), + querier: None, + }; + let instr3 = QueryResponse { + query_id: 72, + max_weight: Weight::zero(), + response: Response::Version(3), + querier: None, + }; let mut sent = take_sent_xcm(); sent.sort_by_key(|k| match (k.1).0[0] { QueryResponse { query_id: q, .. } => q, @@ -802,20 +945,18 @@ fn subscription_side_upgrades_work_with_notify() { assert_eq!( sent, vec![ - (Parachain(1000).into(), Xcm(vec![instr0])), (Parachain(1001).into(), Xcm(vec![instr1])), - (Parachain(1002).into(), Xcm(vec![instr2])), + (Parachain(1003).into(), Xcm(vec![instr3])), ] ); let mut contents = VersionNotifyTargets::::iter().collect::>(); - contents.sort_by_key(|k| k.2); + contents.sort_by_key(|k| k.2 .0); assert_eq!( contents, vec![ - (2, Parachain(1000).into().versioned(), (69, 0, 2)), - (2, Parachain(1001).into().versioned(), (70, 0, 2)), - (2, Parachain(1002).into().versioned(), (71, 0, 2)), + (XCM_VERSION, Parachain(1001).into_versioned(), (70, Weight::zero(), 3)), + (XCM_VERSION, Parachain(1003).into_versioned(), (72, Weight::zero(), 3)), ] ); }); @@ -824,27 +965,23 @@ fn subscription_side_upgrades_work_with_notify() { #[test] fn subscription_side_upgrades_work_without_notify() { new_test_ext_with_balances(vec![]).execute_with(|| { - // An entry from a previous runtime with v0 XCM. - let v0_location = xcm::v0::MultiLocation::X1(xcm::v0::Junction::Parachain(1000)); - let v0_location = VersionedMultiLocation::from(v0_location); - VersionNotifyTargets::::insert(0, v0_location, (69, 0, 2)); - let v1_location = Parachain(1001).into().versioned(); - VersionNotifyTargets::::insert(1, v1_location, (70, 0, 2)); - let v2_location = Parachain(1002).into().versioned(); - VersionNotifyTargets::::insert(2, v2_location, (71, 0, 2)); + // An entry from a previous runtime with v2 XCM. + let v2_location = VersionedMultiLocation::V2(xcm::v2::Junction::Parachain(1001).into()); + VersionNotifyTargets::::insert(1, v2_location, (70, Weight::zero(), 2)); + let v3_location = Parachain(1003).into_versioned(); + VersionNotifyTargets::::insert(3, v3_location, (72, Weight::zero(), 2)); // A runtime upgrade which alters the version does send notifications. XcmPallet::on_runtime_upgrade(); XcmPallet::on_initialize(1); let mut contents = VersionNotifyTargets::::iter().collect::>(); - contents.sort_by_key(|k| k.2); + contents.sort_by_key(|k| k.2 .0); assert_eq!( contents, vec![ - (2, Parachain(1000).into().versioned(), (69, 0, 2)), - (2, Parachain(1001).into().versioned(), (70, 0, 2)), - (2, Parachain(1002).into().versioned(), (71, 0, 2)), + (XCM_VERSION, Parachain(1001).into_versioned(), (70, Weight::zero(), 3)), + (XCM_VERSION, Parachain(1003).into_versioned(), (72, Weight::zero(), 3)), ] ); }); @@ -853,7 +990,7 @@ fn subscription_side_upgrades_work_without_notify() { #[test] fn subscriber_side_subscription_works() { new_test_ext_with_balances(vec![]).execute_with(|| { - let remote = Parachain(1000).into(); + let remote: MultiLocation = Parachain(1000).into(); assert_ok!(XcmPallet::force_subscribe_version_notify( RuntimeOrigin::root(), Box::new(remote.clone().into()), @@ -864,22 +1001,34 @@ fn subscriber_side_subscription_works() { let weight = BaseXcmWeight::get(); let message = Xcm(vec![ - // Remote supports XCM v1 - QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(1) }, + // Remote supports XCM v2 + QueryResponse { + query_id: 0, + max_weight: Weight::zero(), + response: Response::Version(1), + querier: None, + }, ]); - let r = XcmExecutor::::execute_xcm(remote.clone(), message, weight); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm(remote.clone(), message, hash, weight); assert_eq!(r, Outcome::Complete(weight)); assert_eq!(take_sent_xcm(), vec![]); - // This message cannot be sent to a v1 remote. - let v2_msg = Xcm::<()>(vec![Trap(0)]); + // This message cannot be sent to a v2 remote. + let v2_msg = xcm::v2::Xcm::<()>(vec![xcm::v2::Instruction::Trap(0)]); assert_eq!(XcmPallet::wrap_version(&remote, v2_msg.clone()), Err(())); let message = Xcm(vec![ // Remote upgraded to XCM v2 - QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(2) }, + QueryResponse { + query_id: 0, + max_weight: Weight::zero(), + response: Response::Version(2), + querier: None, + }, ]); - let r = XcmExecutor::::execute_xcm(remote.clone(), message, weight); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm(remote.clone(), message, hash, weight); assert_eq!(r, Outcome::Complete(weight)); // This message can now be sent to remote as it's v2. @@ -894,77 +1043,99 @@ fn subscriber_side_subscription_works() { #[test] fn auto_subscription_works() { new_test_ext_with_balances(vec![]).execute_with(|| { - let remote0 = Parachain(1000).into(); - let remote1 = Parachain(1001).into(); + let remote_v2: MultiLocation = Parachain(1000).into(); + let remote_v3: MultiLocation = Parachain(1001).into(); - assert_ok!(XcmPallet::force_default_xcm_version(RuntimeOrigin::root(), Some(1))); + assert_ok!(XcmPallet::force_default_xcm_version(RuntimeOrigin::root(), Some(2))); // Wrapping a version for a destination we don't know elicits a subscription. - let v1_msg = xcm::v1::Xcm::<()>::QueryResponse { - query_id: 1, - response: xcm::v1::Response::Assets(vec![].into()), - }; - let v2_msg = Xcm::<()>(vec![Trap(0)]); + let msg_v2 = xcm::v2::Xcm::<()>(vec![xcm::v2::Instruction::Trap(0)]); + let msg_v3 = xcm::v3::Xcm::<()>(vec![xcm::v3::Instruction::ClearTopic]); assert_eq!( - XcmPallet::wrap_version(&remote0, v1_msg.clone()), - Ok(VersionedXcm::from(v1_msg.clone())), + XcmPallet::wrap_version(&remote_v2, msg_v2.clone()), + Ok(VersionedXcm::from(msg_v2.clone())), ); - assert_eq!(XcmPallet::wrap_version(&remote0, v2_msg.clone()), Err(())); - let expected = vec![(remote0.clone().into(), 2)]; + assert_eq!(XcmPallet::wrap_version(&remote_v2, msg_v3.clone()), Err(())); + + let expected = vec![(remote_v2.clone().into(), 2)]; assert_eq!(VersionDiscoveryQueue::::get().into_inner(), expected); - assert_eq!(XcmPallet::wrap_version(&remote0, v2_msg.clone()), Err(())); - assert_eq!(XcmPallet::wrap_version(&remote1, v2_msg.clone()), Err(())); - let expected = vec![(remote0.clone().into(), 3), (remote1.clone().into(), 1)]; + assert_eq!( + XcmPallet::wrap_version(&remote_v3, msg_v2.clone()), + Ok(VersionedXcm::from(msg_v2.clone())), + ); + assert_eq!(XcmPallet::wrap_version(&remote_v3, msg_v3.clone()), Err(())); + + let expected = vec![(remote_v2.clone().into(), 2), (remote_v3.clone().into(), 2)]; assert_eq!(VersionDiscoveryQueue::::get().into_inner(), expected); XcmPallet::on_initialize(1); assert_eq!( take_sent_xcm(), vec![( - remote0.clone(), - Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: 0 }]), + remote_v3.clone(), + Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: Weight::zero() }]), )] ); - // Assume remote0 is working ok and XCM version 2. + // Assume remote_v3 is working ok and XCM version 3. let weight = BaseXcmWeight::get(); let message = Xcm(vec![ - // Remote supports XCM v2 - QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(2) }, + // Remote supports XCM v3 + QueryResponse { + query_id: 0, + max_weight: Weight::zero(), + response: Response::Version(3), + querier: None, + }, ]); - let r = XcmExecutor::::execute_xcm(remote0.clone(), message, weight); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm(remote_v3.clone(), message, hash, weight); assert_eq!(r, Outcome::Complete(weight)); - // This message can now be sent to remote0 as it's v2. + // V2 messages can be sent to remote_v3 under XCM v3. assert_eq!( - XcmPallet::wrap_version(&remote0, v2_msg.clone()), - Ok(VersionedXcm::from(v2_msg.clone())) + XcmPallet::wrap_version(&remote_v3, msg_v2.clone()), + Ok(VersionedXcm::from(msg_v2.clone()).into_version(3).unwrap()), + ); + // This message can now be sent to remote_v3 as it's v3. + assert_eq!( + XcmPallet::wrap_version(&remote_v3, msg_v3.clone()), + Ok(VersionedXcm::from(msg_v3.clone())) ); XcmPallet::on_initialize(2); assert_eq!( take_sent_xcm(), vec![( - remote1.clone(), - Xcm(vec![SubscribeVersion { query_id: 1, max_response_weight: 0 }]), + remote_v2.clone(), + Xcm(vec![SubscribeVersion { query_id: 1, max_response_weight: Weight::zero() }]), )] ); - // Assume remote1 is working ok and XCM version 1. + // Assume remote_v2 is working ok and XCM version 2. let weight = BaseXcmWeight::get(); let message = Xcm(vec![ - // Remote supports XCM v1 - QueryResponse { query_id: 1, max_weight: 0, response: Response::Version(1) }, + // Remote supports XCM v2 + QueryResponse { + query_id: 1, + max_weight: Weight::zero(), + response: Response::Version(2), + querier: None, + }, ]); - let r = XcmExecutor::::execute_xcm(remote1.clone(), message, weight); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm(remote_v2.clone(), message, hash, weight); assert_eq!(r, Outcome::Complete(weight)); - // v2 messages cannot be sent to remote1... - assert_eq!(XcmPallet::wrap_version(&remote1, v1_msg.clone()), Ok(VersionedXcm::V1(v1_msg))); - assert_eq!(XcmPallet::wrap_version(&remote1, v2_msg.clone()), Err(())); + // v3 messages cannot be sent to remote_v2... + assert_eq!( + XcmPallet::wrap_version(&remote_v2, msg_v2.clone()), + Ok(VersionedXcm::V2(msg_v2)) + ); + assert_eq!(XcmPallet::wrap_version(&remote_v2, msg_v3.clone()), Err(())); }) } @@ -974,16 +1145,15 @@ fn subscription_side_upgrades_work_with_multistage_notify() { AdvertisedXcmVersion::set(1); // An entry from a previous runtime with v0 XCM. - let v0_location = xcm::v0::MultiLocation::X1(xcm::v0::Junction::Parachain(1000)); - let v0_location = VersionedMultiLocation::from(v0_location); - VersionNotifyTargets::::insert(0, v0_location, (69, 0, 1)); - let v1_location = Parachain(1001).into().versioned(); - VersionNotifyTargets::::insert(1, v1_location, (70, 0, 1)); - let v2_location = Parachain(1002).into().versioned(); - VersionNotifyTargets::::insert(2, v2_location, (71, 0, 1)); + let v2_location = VersionedMultiLocation::V2(xcm::v2::Junction::Parachain(1001).into()); + VersionNotifyTargets::::insert(1, v2_location, (70, Weight::zero(), 1)); + let v2_location = VersionedMultiLocation::V2(xcm::v2::Junction::Parachain(1002).into()); + VersionNotifyTargets::::insert(2, v2_location, (71, Weight::zero(), 1)); + let v3_location = Parachain(1003).into_versioned(); + VersionNotifyTargets::::insert(3, v3_location, (72, Weight::zero(), 1)); // New version. - AdvertisedXcmVersion::set(2); + AdvertisedXcmVersion::set(3); // A runtime upgrade which alters the version does send notifications. XcmPallet::on_runtime_upgrade(); @@ -996,9 +1166,24 @@ fn subscription_side_upgrades_work_with_multistage_notify() { } assert_eq!(counter, 4); - let instr0 = QueryResponse { query_id: 69, max_weight: 0, response: Response::Version(2) }; - let instr1 = QueryResponse { query_id: 70, max_weight: 0, response: Response::Version(2) }; - let instr2 = QueryResponse { query_id: 71, max_weight: 0, response: Response::Version(2) }; + let instr1 = QueryResponse { + query_id: 70, + max_weight: Weight::zero(), + response: Response::Version(3), + querier: None, + }; + let instr2 = QueryResponse { + query_id: 71, + max_weight: Weight::zero(), + response: Response::Version(3), + querier: None, + }; + let instr3 = QueryResponse { + query_id: 72, + max_weight: Weight::zero(), + response: Response::Version(3), + querier: None, + }; let mut sent = take_sent_xcm(); sent.sort_by_key(|k| match (k.1).0[0] { QueryResponse { query_id: q, .. } => q, @@ -1007,20 +1192,20 @@ fn subscription_side_upgrades_work_with_multistage_notify() { assert_eq!( sent, vec![ - (Parachain(1000).into(), Xcm(vec![instr0])), (Parachain(1001).into(), Xcm(vec![instr1])), (Parachain(1002).into(), Xcm(vec![instr2])), + (Parachain(1003).into(), Xcm(vec![instr3])), ] ); let mut contents = VersionNotifyTargets::::iter().collect::>(); - contents.sort_by_key(|k| k.2); + contents.sort_by_key(|k| k.2 .0); assert_eq!( contents, vec![ - (2, Parachain(1000).into().versioned(), (69, 0, 2)), - (2, Parachain(1001).into().versioned(), (70, 0, 2)), - (2, Parachain(1002).into().versioned(), (71, 0, 2)), + (XCM_VERSION, Parachain(1001).into_versioned(), (70, Weight::zero(), 3)), + (XCM_VERSION, Parachain(1002).into_versioned(), (71, Weight::zero(), 3)), + (XCM_VERSION, Parachain(1003).into_versioned(), (72, Weight::zero(), 3)), ] ); }); diff --git a/xcm/procedural/src/lib.rs b/xcm/procedural/src/lib.rs index 8e43569b64d7..36181ccbd1fb 100644 --- a/xcm/procedural/src/lib.rs +++ b/xcm/procedural/src/lib.rs @@ -18,25 +18,32 @@ use proc_macro::TokenStream; -mod v0; -mod v1; +mod v2; +mod v3; mod weight_info; #[proc_macro] -pub fn impl_conversion_functions_for_multilocation_v0(input: TokenStream) -> TokenStream { - v0::multilocation::generate_conversion_functions(input) +pub fn impl_conversion_functions_for_multilocation_v2(input: TokenStream) -> TokenStream { + v2::multilocation::generate_conversion_functions(input) .unwrap_or_else(syn::Error::into_compile_error) .into() } +#[proc_macro_derive(XcmWeightInfoTrait)] +pub fn derive_xcm_weight_info(item: TokenStream) -> TokenStream { + weight_info::derive(item) +} + #[proc_macro] -pub fn impl_conversion_functions_for_multilocation_v1(input: TokenStream) -> TokenStream { - v1::multilocation::generate_conversion_functions(input) +pub fn impl_conversion_functions_for_multilocation_v3(input: TokenStream) -> TokenStream { + v3::multilocation::generate_conversion_functions(input) .unwrap_or_else(syn::Error::into_compile_error) .into() } -#[proc_macro_derive(XcmWeightInfoTrait)] -pub fn derive_xcm_weight_info(item: TokenStream) -> TokenStream { - weight_info::derive(item) +#[proc_macro] +pub fn impl_conversion_functions_for_junctions_v3(input: TokenStream) -> TokenStream { + v3::junctions::generate_conversion_functions(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } diff --git a/xcm/procedural/src/v0/multilocation.rs b/xcm/procedural/src/v0/multilocation.rs deleted file mode 100644 index 82db5cce4ee5..000000000000 --- a/xcm/procedural/src/v0/multilocation.rs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot 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. - -// Polkadot 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 Polkadot. If not, see . - -use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote}; - -pub fn generate_conversion_functions(input: proc_macro::TokenStream) -> syn::Result { - if !input.is_empty() { - return Err(syn::Error::new(Span::call_site(), "No arguments expected")) - } - - let from_tuples = generate_conversion_from_tuples(); - let from_v1 = generate_conversion_from_v1(); - - Ok(quote! { - #from_tuples - #from_v1 - }) -} - -fn generate_conversion_from_tuples() -> TokenStream { - let from_tuples = (0..8usize) - .map(|num_junctions| { - let junctions = - (0..=num_junctions).map(|_| format_ident!("Junction")).collect::>(); - let idents = (0..=num_junctions).map(|i| format_ident!("j{}", i)).collect::>(); - let variant = &format_ident!("X{}", num_junctions + 1); - let array_size = num_junctions + 1; - - quote! { - impl From<( #(#junctions,)* )> for MultiLocation { - fn from( ( #(#idents,)* ): ( #(#junctions,)* ) ) -> Self { - MultiLocation::#variant( #(#idents),* ) - } - } - - impl From<[Junction; #array_size]> for MultiLocation { - fn from(j: [Junction; #array_size]) -> Self { - let [#(#idents),*] = j; - MultiLocation::#variant( #(#idents),* ) - } - } - } - }) - .collect::(); - - quote! { - impl From<()> for MultiLocation { - fn from(_: ()) -> Self { - MultiLocation::Null - } - } - - impl From for MultiLocation { - fn from(x: Junction) -> Self { - MultiLocation::X1(x) - } - } - - impl From<[Junction; 0]> for MultiLocation { - fn from(_: [Junction; 0]) -> Self { - MultiLocation::Null - } - } - - #from_tuples - } -} - -fn generate_conversion_from_v1() -> TokenStream { - let match_variants = (0..8u8) - .map(|cur_num| { - let variant = format_ident!("X{}", cur_num + 1); - let idents = (1..=cur_num).map(|i| format_ident!("j{}", i)).collect::>(); - - quote! { - crate::v1::Junctions::#variant( j0 #(, #idents)* ) => res - .pushed_with(Junction::from(j0)) - #( .and_then(|res| res.pushed_with(Junction::from(#idents))) )* - .map_err(|_| ()), - } - }) - .collect::(); - - quote! { - impl TryFrom for MultiLocation { - type Error = (); - fn try_from(v1: crate::v1::MultiLocation) -> core::result::Result { - let mut res = MultiLocation::Null; - - for _ in 0..v1.parents { - res.push(Junction::Parent)?; - } - - match v1.interior { - crate::v1::Junctions::Here => Ok(res), - #match_variants - } - } - } - } -} diff --git a/xcm/procedural/src/v1.rs b/xcm/procedural/src/v1.rs deleted file mode 100644 index 7774df4e9f8f..000000000000 --- a/xcm/procedural/src/v1.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot 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. - -// Polkadot 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 Polkadot. If not, see . - -pub mod multilocation; diff --git a/xcm/procedural/src/v1/multilocation.rs b/xcm/procedural/src/v1/multilocation.rs deleted file mode 100644 index 0d8ab5452b2e..000000000000 --- a/xcm/procedural/src/v1/multilocation.rs +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot 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. - -// Polkadot 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 Polkadot. If not, see . - -use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote}; -use syn::{Result, Token}; - -pub fn generate_conversion_functions(input: proc_macro::TokenStream) -> Result { - if !input.is_empty() { - return Err(syn::Error::new(Span::call_site(), "No arguments expected")) - } - - // Support up to 8 Parents in a tuple, assuming that most use cases don't go past 8 parents. - let from_tuples = generate_conversion_from_tuples(8); - let from_v0 = generate_conversion_from_v0(); - - Ok(quote! { - #from_tuples - #from_v0 - }) -} - -fn generate_conversion_from_tuples(max_parents: u8) -> TokenStream { - let mut from_tuples = (0..8usize) - .map(|num_junctions| { - let junctions = - (0..=num_junctions).map(|_| format_ident!("Junction")).collect::>(); - let idents = (0..=num_junctions).map(|i| format_ident!("j{}", i)).collect::>(); - let variant = &format_ident!("X{}", num_junctions + 1); - let array_size = num_junctions + 1; - - let mut from_tuple = quote! { - impl From<( #(#junctions,)* )> for MultiLocation { - fn from( ( #(#idents,)* ): ( #(#junctions,)* ) ) -> Self { - MultiLocation { parents: 0, interior: Junctions::#variant( #(#idents),* ) } - } - } - - impl From<(u8, #(#junctions),*)> for MultiLocation { - fn from( ( parents, #(#idents),* ): (u8, #(#junctions),* ) ) -> Self { - MultiLocation { parents, interior: Junctions::#variant( #(#idents),* ) } - } - } - - impl From<(Ancestor, #(#junctions),*)> for MultiLocation { - fn from( ( Ancestor(parents), #(#idents),* ): (Ancestor, #(#junctions),* ) ) -> Self { - MultiLocation { parents, interior: Junctions::#variant( #(#idents),* ) } - } - } - - impl From<[Junction; #array_size]> for MultiLocation { - fn from(j: [Junction; #array_size]) -> Self { - let [#(#idents),*] = j; - MultiLocation { parents: 0, interior: Junctions::#variant( #(#idents),* ) } - } - } - }; - - let from_parent_tuples = (1..=max_parents).map(|cur_parents| { - let parents = (0..cur_parents).map(|_| format_ident!("Parent")).collect::>(); - let underscores = - (0..cur_parents).map(|_| Token![_](Span::call_site())).collect::>(); - - quote! { - impl From<( #(#parents,)* #(#junctions),* )> for MultiLocation { - fn from( (#(#underscores,)* #(#idents),*): ( #(#parents,)* #(#junctions),* ) ) -> Self { - MultiLocation { parents: #cur_parents, interior: Junctions::#variant( #(#idents),* ) } - } - } - } - }); - - from_tuple.extend(from_parent_tuples); - from_tuple - }) - .collect::(); - - let from_parent_junctions_tuples = (1..=max_parents).map(|cur_parents| { - let parents = (0..cur_parents).map(|_| format_ident!("Parent")).collect::>(); - let underscores = - (0..cur_parents).map(|_| Token![_](Span::call_site())).collect::>(); - - quote! { - impl From<( #(#parents,)* Junctions )> for MultiLocation { - fn from( (#(#underscores,)* junctions): ( #(#parents,)* Junctions ) ) -> Self { - MultiLocation { parents: #cur_parents, interior: junctions } - } - } - } - }); - from_tuples.extend(from_parent_junctions_tuples); - - quote! { - impl From for MultiLocation { - fn from(junctions: Junctions) -> Self { - MultiLocation { parents: 0, interior: junctions } - } - } - - impl From<(u8, Junctions)> for MultiLocation { - fn from((parents, interior): (u8, Junctions)) -> Self { - MultiLocation { parents, interior } - } - } - - impl From<(Ancestor, Junctions)> for MultiLocation { - fn from((Ancestor(parents), interior): (Ancestor, Junctions)) -> Self { - MultiLocation { parents, interior } - } - } - - impl From<()> for MultiLocation { - fn from(_: ()) -> Self { - MultiLocation { parents: 0, interior: Junctions::Here } - } - } - - impl From<(u8,)> for MultiLocation { - fn from((parents,): (u8,)) -> Self { - MultiLocation { parents, interior: Junctions::Here } - } - } - - impl From for MultiLocation { - fn from(x: Junction) -> Self { - MultiLocation { parents: 0, interior: Junctions::X1(x) } - } - } - - impl From<[Junction; 0]> for MultiLocation { - fn from(_: [Junction; 0]) -> Self { - MultiLocation { parents: 0, interior: Junctions::Here } - } - } - - #from_tuples - } -} - -fn generate_conversion_from_v0() -> TokenStream { - let match_variants = (0..8u8) - .map(|cur_num| { - let num_ancestors = cur_num + 1; - let variant = format_ident!("X{}", num_ancestors); - let idents = (0..=cur_num).map(|i| format_ident!("j{}", i)).collect::>(); - - let intermediate_match_arms = (1..num_ancestors) - .rev() - .map(|parent_count| { - let parent_idents = - (0..parent_count).map(|j| format_ident!("j{}", j)).collect::>(); - let junction_idents = (parent_count..num_ancestors) - .map(|j| format_ident!("j{}", j)) - .collect::>(); - let junction_variant = format_ident!("X{}", num_ancestors - parent_count); - - quote! { - crate::v0::MultiLocation::#variant( #(#idents),* ) - if #( #parent_idents.is_parent() )&&* => - Ok(MultiLocation { - parents: #parent_count, - interior: #junction_variant( #( TryInto::try_into(#junction_idents)? ),* ), - }), - } - }) - .collect::(); - - quote! { - crate::v0::MultiLocation::#variant( #(#idents),* ) - if #( #idents.is_parent() )&&* => - Ok(MultiLocation::ancestor(#num_ancestors)), - #intermediate_match_arms - crate::v0::MultiLocation::#variant( #(#idents),* ) => - Ok( #variant( #( TryInto::try_into(#idents)? ),* ).into() ), - } - }) - .collect::(); - - quote! { - impl core::convert::TryFrom for MultiLocation { - type Error = (); - fn try_from(mut v0: crate::v0::MultiLocation) -> core::result::Result { - use Junctions::*; - - v0.canonicalize(); - match v0 { - crate::v0::MultiLocation::Null => Ok(Here.into()), - #match_variants - } - } - } - } -} diff --git a/xcm/procedural/src/v2.rs b/xcm/procedural/src/v2.rs new file mode 100644 index 000000000000..6fbeeefeb128 --- /dev/null +++ b/xcm/procedural/src/v2.rs @@ -0,0 +1,183 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +pub mod multilocation { + use proc_macro2::{Span, TokenStream}; + use quote::{format_ident, quote}; + use syn::{Result, Token}; + + pub fn generate_conversion_functions(input: proc_macro::TokenStream) -> Result { + if !input.is_empty() { + return Err(syn::Error::new(Span::call_site(), "No arguments expected")) + } + + // Support up to 8 Parents in a tuple, assuming that most use cases don't go past 8 parents. + let from_tuples = generate_conversion_from_tuples(8); + let from_v3 = generate_conversion_from_v3(); + + Ok(quote! { + #from_tuples + #from_v3 + }) + } + + fn generate_conversion_from_tuples(max_parents: u8) -> TokenStream { + let mut from_tuples = (0..8usize) + .map(|num_junctions| { + let junctions = + (0..=num_junctions).map(|_| format_ident!("Junction")).collect::>(); + let idents = + (0..=num_junctions).map(|i| format_ident!("j{}", i)).collect::>(); + let variant = &format_ident!("X{}", num_junctions + 1); + let array_size = num_junctions + 1; + + let mut from_tuple = quote! { + impl From<( #(#junctions,)* )> for MultiLocation { + fn from( ( #(#idents,)* ): ( #(#junctions,)* ) ) -> Self { + MultiLocation { parents: 0, interior: Junctions::#variant( #(#idents),* ) } + } + } + + impl From<(u8, #(#junctions),*)> for MultiLocation { + fn from( ( parents, #(#idents),* ): (u8, #(#junctions),* ) ) -> Self { + MultiLocation { parents, interior: Junctions::#variant( #(#idents),* ) } + } + } + + impl From<(Ancestor, #(#junctions),*)> for MultiLocation { + fn from( ( Ancestor(parents), #(#idents),* ): (Ancestor, #(#junctions),* ) ) -> Self { + MultiLocation { parents, interior: Junctions::#variant( #(#idents),* ) } + } + } + + impl From<[Junction; #array_size]> for MultiLocation { + fn from(j: [Junction; #array_size]) -> Self { + let [#(#idents),*] = j; + MultiLocation { parents: 0, interior: Junctions::#variant( #(#idents),* ) } + } + } + }; + + let from_parent_tuples = (1..=max_parents).map(|cur_parents| { + let parents = + (0..cur_parents).map(|_| format_ident!("Parent")).collect::>(); + let underscores = + (0..cur_parents).map(|_| Token![_](Span::call_site())).collect::>(); + + quote! { + impl From<( #(#parents,)* #(#junctions),* )> for MultiLocation { + fn from( (#(#underscores,)* #(#idents),*): ( #(#parents,)* #(#junctions),* ) ) -> Self { + MultiLocation { parents: #cur_parents, interior: Junctions::#variant( #(#idents),* ) } + } + } + } + }); + + from_tuple.extend(from_parent_tuples); + from_tuple + }) + .collect::(); + + let from_parent_junctions_tuples = (1..=max_parents).map(|cur_parents| { + let parents = (0..cur_parents).map(|_| format_ident!("Parent")).collect::>(); + let underscores = + (0..cur_parents).map(|_| Token![_](Span::call_site())).collect::>(); + + quote! { + impl From<( #(#parents,)* Junctions )> for MultiLocation { + fn from( (#(#underscores,)* junctions): ( #(#parents,)* Junctions ) ) -> Self { + MultiLocation { parents: #cur_parents, interior: junctions } + } + } + } + }); + from_tuples.extend(from_parent_junctions_tuples); + + quote! { + impl From for MultiLocation { + fn from(junctions: Junctions) -> Self { + MultiLocation { parents: 0, interior: junctions } + } + } + + impl From<(u8, Junctions)> for MultiLocation { + fn from((parents, interior): (u8, Junctions)) -> Self { + MultiLocation { parents, interior } + } + } + + impl From<(Ancestor, Junctions)> for MultiLocation { + fn from((Ancestor(parents), interior): (Ancestor, Junctions)) -> Self { + MultiLocation { parents, interior } + } + } + + impl From<()> for MultiLocation { + fn from(_: ()) -> Self { + MultiLocation { parents: 0, interior: Junctions::Here } + } + } + + impl From<(u8,)> for MultiLocation { + fn from((parents,): (u8,)) -> Self { + MultiLocation { parents, interior: Junctions::Here } + } + } + + impl From for MultiLocation { + fn from(x: Junction) -> Self { + MultiLocation { parents: 0, interior: Junctions::X1(x) } + } + } + + impl From<[Junction; 0]> for MultiLocation { + fn from(_: [Junction; 0]) -> Self { + MultiLocation { parents: 0, interior: Junctions::Here } + } + } + + #from_tuples + } + } + + fn generate_conversion_from_v3() -> TokenStream { + let match_variants = (0..8u8) + .map(|cur_num| { + let num_ancestors = cur_num + 1; + let variant = format_ident!("X{}", num_ancestors); + let idents = (0..=cur_num).map(|i| format_ident!("j{}", i)).collect::>(); + + quote! { + crate::v3::Junctions::#variant( #(#idents),* ) => + #variant( #( core::convert::TryInto::try_into(#idents)? ),* ), + } + }) + .collect::(); + + quote! { + impl core::convert::TryFrom for Junctions { + type Error = (); + fn try_from(mut new: crate::v3::Junctions) -> core::result::Result { + use Junctions::*; + Ok(match new { + crate::v3::Junctions::Here => Here, + #match_variants + }) + } + } + } + } +} diff --git a/xcm/procedural/src/v3.rs b/xcm/procedural/src/v3.rs new file mode 100644 index 000000000000..0897481d0949 --- /dev/null +++ b/xcm/procedural/src/v3.rs @@ -0,0 +1,186 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote}; +use syn::{Result, Token}; + +const MAX_JUNCTIONS: usize = 8; + +pub mod multilocation { + use super::*; + + pub fn generate_conversion_functions(input: proc_macro::TokenStream) -> Result { + if !input.is_empty() { + return Err(syn::Error::new(Span::call_site(), "No arguments expected")) + } + + let from_tuples = generate_conversion_from_tuples(8, 8); + + Ok(quote! { + #from_tuples + }) + } + + fn generate_conversion_from_tuples(max_junctions: usize, max_parents: usize) -> TokenStream { + let mut from_tuples = (0..=max_junctions) + .map(|num_junctions| { + let types = (0..num_junctions).map(|i| format_ident!("J{}", i)).collect::>(); + let idents = + (0..num_junctions).map(|i| format_ident!("j{}", i)).collect::>(); + let array_size = num_junctions; + let interior = if num_junctions == 0 { + quote!(Junctions::Here) + } else { + let variant = format_ident!("X{}", num_junctions); + quote! { + Junctions::#variant( #(#idents .into()),* ) + } + }; + + let mut from_tuple = quote! { + impl< #(#types : Into,)* > From<( Ancestor, #( #types ),* )> for MultiLocation { + fn from( ( Ancestor(parents), #(#idents),* ): ( Ancestor, #( #types ),* ) ) -> Self { + MultiLocation { parents, interior: #interior } + } + } + + impl From<[Junction; #array_size]> for MultiLocation { + fn from(j: [Junction; #array_size]) -> Self { + let [#(#idents),*] = j; + MultiLocation { parents: 0, interior: #interior } + } + } + }; + + let from_parent_tuples = (0..=max_parents).map(|cur_parents| { + let parents = + (0..cur_parents).map(|_| format_ident!("Parent")).collect::>(); + let underscores = + (0..cur_parents).map(|_| Token![_](Span::call_site())).collect::>(); + + quote! { + impl< #(#types : Into,)* > From<( #( #parents , )* #( #types , )* )> for MultiLocation { + fn from( ( #(#underscores,)* #(#idents,)* ): ( #(#parents,)* #(#types,)* ) ) -> Self { + Self { parents: #cur_parents as u8, interior: #interior } + } + } + } + }); + + from_tuple.extend(from_parent_tuples); + from_tuple + }) + .collect::(); + + let from_parent_junctions_tuples = (0..=max_parents).map(|cur_parents| { + let parents = (0..cur_parents).map(|_| format_ident!("Parent")).collect::>(); + let underscores = + (0..cur_parents).map(|_| Token![_](Span::call_site())).collect::>(); + + quote! { + impl From<( #(#parents,)* Junctions )> for MultiLocation { + fn from( (#(#underscores,)* junctions): ( #(#parents,)* Junctions ) ) -> Self { + MultiLocation { parents: #cur_parents as u8, interior: junctions } + } + } + } + }); + from_tuples.extend(from_parent_junctions_tuples); + + quote! { + impl From<(Ancestor, Junctions)> for MultiLocation { + fn from((Ancestor(parents), interior): (Ancestor, Junctions)) -> Self { + MultiLocation { parents, interior } + } + } + + impl From for MultiLocation { + fn from(x: Junction) -> Self { + MultiLocation { parents: 0, interior: Junctions::X1(x) } + } + } + + #from_tuples + } + } +} + +pub mod junctions { + use super::*; + + pub fn generate_conversion_functions(input: proc_macro::TokenStream) -> Result { + if !input.is_empty() { + return Err(syn::Error::new(Span::call_site(), "No arguments expected")) + } + + // Support up to 8 Parents in a tuple, assuming that most use cases don't go past 8 parents. + let from_v2 = generate_conversion_from_v2(MAX_JUNCTIONS); + let from_tuples = generate_conversion_from_tuples(MAX_JUNCTIONS); + + Ok(quote! { + #from_v2 + #from_tuples + }) + } + + fn generate_conversion_from_tuples(max_junctions: usize) -> TokenStream { + (1..=max_junctions) + .map(|num_junctions| { + let idents = + (0..num_junctions).map(|i| format_ident!("j{}", i)).collect::>(); + let types = (0..num_junctions).map(|i| format_ident!("J{}", i)).collect::>(); + let variant = &format_ident!("X{}", num_junctions); + + quote! { + impl<#(#types : Into,)*> From<( #(#types,)* )> for Junctions { + fn from( ( #(#idents,)* ): ( #(#types,)* ) ) -> Self { + Self::#variant( #(#idents .into()),* ) + } + } + } + }) + .collect() + } + + fn generate_conversion_from_v2(max_junctions: usize) -> TokenStream { + let match_variants = (0..max_junctions) + .map(|cur_num| { + let num_ancestors = cur_num + 1; + let variant = format_ident!("X{}", num_ancestors); + let idents = (0..=cur_num).map(|i| format_ident!("j{}", i)).collect::>(); + + quote! { + crate::v2::Junctions::#variant( #(#idents),* ) => + #variant( #( core::convert::TryInto::try_into(#idents)? ),* ), + } + }) + .collect::(); + + quote! { + impl core::convert::TryFrom for Junctions { + type Error = (); + fn try_from(mut old: crate::v2::Junctions) -> core::result::Result { + use Junctions::*; + Ok(match old { + crate::v2::Junctions::Here => Here, + #match_variants + }) + } + } + } + } +} diff --git a/xcm/src/lib.rs b/xcm/src/lib.rs index 6396f2228e33..678a825b6608 100644 --- a/xcm/src/lib.rs +++ b/xcm/src/lib.rs @@ -23,17 +23,19 @@ #![no_std] extern crate alloc; -use alloc::vec::Vec; use derivative::Derivative; -use parity_scale_codec::{Decode, Encode, Error as CodecError, Input}; +use parity_scale_codec::{Decode, Encode, Error as CodecError, Input, MaxEncodedLen}; use scale_info::TypeInfo; -pub mod v0; -pub mod v1; pub mod v2; +pub mod v3; + +pub mod lts { + pub use super::v3::*; +} pub mod latest { - pub use super::v2::*; + pub use super::v3::*; } mod double_encoded; @@ -65,241 +67,284 @@ pub trait IntoVersion: Sized { } } -/// A single `MultiLocation` value, together with its version code. -#[derive(Derivative, Encode, Decode, TypeInfo)] -#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] -#[codec(encode_bound())] -#[codec(decode_bound())] -pub enum VersionedMultiLocation { - V0(v0::MultiLocation), - V1(v1::MultiLocation), -} - -impl IntoVersion for VersionedMultiLocation { - fn into_version(self, n: Version) -> Result { - Ok(match n { - 0 => Self::V0(self.try_into()?), - 1 | 2 => Self::V1(self.try_into()?), - _ => return Err(()), - }) - } -} - -impl From for VersionedMultiLocation { - fn from(x: v0::MultiLocation) -> Self { - VersionedMultiLocation::V0(x) - } -} - -impl> From for VersionedMultiLocation { - fn from(x: T) -> Self { - VersionedMultiLocation::V1(x.into()) - } -} - -impl TryFrom for v0::MultiLocation { - type Error = (); - fn try_from(x: VersionedMultiLocation) -> Result { - use VersionedMultiLocation::*; - match x { - V0(x) => Ok(x), - V1(x) => x.try_into(), +pub trait TryAs { + fn try_as(&self) -> Result<&T, ()>; +} + +macro_rules! versioned_type { + ($(#[$attr:meta])* pub enum $n:ident { + V3($v3:ty), + }) => { + #[derive(Derivative, Encode, Decode, TypeInfo)] + #[derivative( + Clone(bound = ""), + Eq(bound = ""), + PartialEq(bound = ""), + Debug(bound = "") + )] + #[codec(encode_bound())] + #[codec(decode_bound())] + $(#[$attr])* + pub enum $n { + #[codec(index = 0)] + V3($v3), } - } -} - -impl TryFrom for v1::MultiLocation { - type Error = (); - fn try_from(x: VersionedMultiLocation) -> Result { - use VersionedMultiLocation::*; - match x { - V0(x) => x.try_into(), - V1(x) => Ok(x), + impl $n { + pub fn try_as(&self) -> Result<&T, ()> where Self: TryAs { + >::try_as(&self) + } } - } -} - -/// A single `Response` value, together with its version code. -#[derive(Derivative, Encode, Decode, TypeInfo)] -#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] -#[codec(encode_bound())] -#[codec(decode_bound())] -pub enum VersionedResponse { - V0(v0::Response), - V1(v1::Response), - V2(v2::Response), -} - -impl IntoVersion for VersionedResponse { - fn into_version(self, n: Version) -> Result { - Ok(match n { - 0 => Self::V0(self.try_into()?), - 1 => Self::V1(self.try_into()?), - 2 => Self::V2(self.try_into()?), - _ => return Err(()), - }) - } -} - -impl From for VersionedResponse { - fn from(x: v0::Response) -> Self { - VersionedResponse::V0(x) - } -} - -impl From for VersionedResponse { - fn from(x: v1::Response) -> Self { - VersionedResponse::V1(x) - } -} - -impl> From for VersionedResponse { - fn from(x: T) -> Self { - VersionedResponse::V2(x.into()) - } -} - -impl TryFrom for v0::Response { - type Error = (); - fn try_from(x: VersionedResponse) -> Result { - use VersionedResponse::*; - match x { - V0(x) => Ok(x), - V1(x) => x.try_into(), - V2(x) => VersionedResponse::V1(x.try_into()?).try_into(), + impl TryAs<$v3> for $n { + fn try_as(&self) -> Result<&$v3, ()> { + match &self { + Self::V3(ref x) => Ok(x), + } + } } - } -} - -impl TryFrom for v1::Response { - type Error = (); - fn try_from(x: VersionedResponse) -> Result { - use VersionedResponse::*; - match x { - V0(x) => x.try_into(), - V1(x) => Ok(x), - V2(x) => x.try_into(), + impl IntoVersion for $n { + fn into_version(self, n: Version) -> Result { + Ok(match n { + 3 => Self::V3(self.try_into()?), + _ => return Err(()), + }) + } } - } -} - -impl TryFrom for v2::Response { - type Error = (); - fn try_from(x: VersionedResponse) -> Result { - use VersionedResponse::*; - match x { - V0(x) => VersionedResponse::V1(x.try_into()?).try_into(), - V1(x) => x.try_into(), - V2(x) => Ok(x), + impl> From for $n { + fn from(x: T) -> Self { + $n::V3(x.into()) + } } - } -} - -/// A single `MultiAsset` value, together with its version code. -#[derive(Derivative, Encode, Decode, TypeInfo)] -#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] -#[codec(encode_bound())] -#[codec(decode_bound())] -pub enum VersionedMultiAsset { - V0(v0::MultiAsset), - V1(v1::MultiAsset), -} - -impl IntoVersion for VersionedMultiAsset { - fn into_version(self, n: Version) -> Result { - Ok(match n { - 0 => Self::V0(self.try_into()?), - 1 | 2 => Self::V1(self.try_into()?), - _ => return Err(()), - }) - } -} - -impl From for VersionedMultiAsset { - fn from(x: v0::MultiAsset) -> Self { - VersionedMultiAsset::V0(x) - } -} - -impl> From for VersionedMultiAsset { - fn from(x: T) -> Self { - VersionedMultiAsset::V1(x.into()) - } -} + impl TryFrom<$n> for $v3 { + type Error = (); + fn try_from(x: $n) -> Result { + use $n::*; + match x { + V3(x) => Ok(x), + } + } + } + impl MaxEncodedLen for $n { + fn max_encoded_len() -> usize { + <$v3>::max_encoded_len() + } + } + }; -impl TryFrom for v0::MultiAsset { - type Error = (); - fn try_from(x: VersionedMultiAsset) -> Result { - use VersionedMultiAsset::*; - match x { - V0(x) => Ok(x), - V1(x) => x.try_into(), + ($(#[$attr:meta])* pub enum $n:ident { + V2($v2:ty), + V3($v3:ty), + }) => { + #[derive(Derivative, Encode, Decode, TypeInfo)] + #[derivative( + Clone(bound = ""), + Eq(bound = ""), + PartialEq(bound = ""), + Debug(bound = "") + )] + #[codec(encode_bound())] + #[codec(decode_bound())] + $(#[$attr])* + pub enum $n { + #[codec(index = 0)] + V2($v2), + #[codec(index = 1)] + V3($v3), } - } -} + impl $n { + pub fn try_as(&self) -> Result<&T, ()> where Self: TryAs { + >::try_as(&self) + } + } + impl TryAs<$v2> for $n { + fn try_as(&self) -> Result<&$v2, ()> { + match &self { + Self::V2(ref x) => Ok(x), + _ => Err(()), + } + } + } + impl TryAs<$v3> for $n { + fn try_as(&self) -> Result<&$v3, ()> { + match &self { + Self::V3(ref x) => Ok(x), + _ => Err(()), + } + } + } + impl IntoVersion for $n { + fn into_version(self, n: Version) -> Result { + Ok(match n { + 1 | 2 => Self::V2(self.try_into()?), + 3 => Self::V3(self.try_into()?), + _ => return Err(()), + }) + } + } + impl From<$v2> for $n { + fn from(x: $v2) -> Self { + $n::V2(x) + } + } + impl> From for $n { + fn from(x: T) -> Self { + $n::V3(x.into()) + } + } + impl TryFrom<$n> for $v2 { + type Error = (); + fn try_from(x: $n) -> Result { + use $n::*; + match x { + V2(x) => Ok(x), + V3(x) => x.try_into(), + } + } + } + impl TryFrom<$n> for $v3 { + type Error = (); + fn try_from(x: $n) -> Result { + use $n::*; + match x { + V2(x) => x.try_into(), + V3(x) => Ok(x), + } + } + } + impl MaxEncodedLen for $n { + fn max_encoded_len() -> usize { + <$v3>::max_encoded_len() + } + } + }; -impl TryFrom for v1::MultiAsset { - type Error = (); - fn try_from(x: VersionedMultiAsset) -> Result { - use VersionedMultiAsset::*; - match x { - V0(x) => x.try_into(), - V1(x) => Ok(x), + ($(#[$attr:meta])* pub enum $n:ident { + V2($v2:ty), + V3($v3:ty), + }) => { + #[derive(Derivative, Encode, Decode, TypeInfo)] + #[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] + #[codec(encode_bound())] + #[codec(decode_bound())] + $(#[$attr])* + pub enum $n { + #[codec(index = 1)] + V2($v2), + #[codec(index = 2)] + V3($v3), + } + impl $n { + pub fn try_as(&self) -> Result<&T, ()> where Self: TryAs { + >::try_as(&self) + } + } + impl TryAs<$v2> for $n { + fn try_as(&self) -> Result<&$v2, ()> { + match &self { + Self::V2(ref x) => Ok(x), + _ => Err(()), + } + } + } + impl TryAs<$v3> for $n { + fn try_as(&self) -> Result<&$v3, ()> { + match &self { + Self::V3(ref x) => Ok(x), + _ => Err(()), + } + } + } + impl IntoVersion for $n { + fn into_version(self, n: Version) -> Result { + Ok(match n { + 2 => Self::V2(self.try_into()?), + 3 => Self::V3(self.try_into()?), + _ => return Err(()), + }) + } + } + impl From<$v2> for $n { + fn from(x: $v2) -> Self { + $n::V2(x) + } + } + impl> From for $n { + fn from(x: T) -> Self { + $n::V3(x.into()) + } + } + impl TryFrom<$n> for $v2 { + type Error = (); + fn try_from(x: $n) -> Result { + use $n::*; + match x { + V2(x) => Ok(x), + V3(x) => x.try_into(), + } + } + } + impl TryFrom<$n> for $v3 { + type Error = (); + fn try_from(x: $n) -> Result { + use $n::*; + match x { + V2(x) => x.try_into(), + V3(x) => Ok(x), + } + } + } + impl MaxEncodedLen for $n { + fn max_encoded_len() -> usize { + <$v3>::max_encoded_len() + } } } } -/// A single `MultiAssets` value, together with its version code. -#[derive(Derivative, Encode, Decode, TypeInfo)] -#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] -#[codec(encode_bound())] -#[codec(decode_bound())] -pub enum VersionedMultiAssets { - V0(Vec), - V1(v1::MultiAssets), +versioned_type! { + /// A single version's `Response` value, together with its version code. + pub enum VersionedAssetId { + V3(v3::AssetId), + } } -impl IntoVersion for VersionedMultiAssets { - fn into_version(self, n: Version) -> Result { - Ok(match n { - 0 => Self::V0(self.try_into()?), - 1 | 2 => Self::V1(self.try_into()?), - _ => return Err(()), - }) +versioned_type! { + /// A single version's `Response` value, together with its version code. + pub enum VersionedResponse { + V2(v2::Response), + V3(v3::Response), } } -impl From> for VersionedMultiAssets { - fn from(x: Vec) -> Self { - VersionedMultiAssets::V0(x) +versioned_type! { + /// A single `MultiLocation` value, together with its version code. + #[derive(Ord, PartialOrd)] + pub enum VersionedMultiLocation { + V2(v2::MultiLocation), + V3(v3::MultiLocation), } } -impl> From for VersionedMultiAssets { - fn from(x: T) -> Self { - VersionedMultiAssets::V1(x.into()) +versioned_type! { + /// A single `InteriorMultiLocation` value, together with its version code. + pub enum VersionedInteriorMultiLocation { + V2(v2::InteriorMultiLocation), + V3(v3::InteriorMultiLocation), } } -impl TryFrom for Vec { - type Error = (); - fn try_from(x: VersionedMultiAssets) -> Result { - use VersionedMultiAssets::*; - match x { - V0(x) => Ok(x), - V1(x) => x.try_into(), - } +versioned_type! { + /// A single `MultiAsset` value, together with its version code. + pub enum VersionedMultiAsset { + V2(v2::MultiAsset), + V3(v3::MultiAsset), } } -impl TryFrom for v1::MultiAssets { - type Error = (); - fn try_from(x: VersionedMultiAssets) -> Result { - use VersionedMultiAssets::*; - match x { - V0(x) => x.try_into(), - V1(x) => Ok(x), - } +versioned_type! { + /// A single `MultiAssets` value, together with its version code. + pub enum VersionedMultiAssets { + V2(v2::MultiAssets), + V3(v3::MultiAssets), } } @@ -310,72 +355,52 @@ impl TryFrom for v1::MultiAssets { #[codec(decode_bound())] #[scale_info(bounds(), skip_type_params(RuntimeCall))] pub enum VersionedXcm { - V0(v0::Xcm), - V1(v1::Xcm), + #[codec(index = 2)] V2(v2::Xcm), + #[codec(index = 3)] + V3(v3::Xcm), } impl IntoVersion for VersionedXcm { fn into_version(self, n: Version) -> Result { Ok(match n { - 0 => Self::V0(self.try_into()?), - 1 => Self::V1(self.try_into()?), 2 => Self::V2(self.try_into()?), + 3 => Self::V3(self.try_into()?), _ => return Err(()), }) } } -impl From> for VersionedXcm { - fn from(x: v0::Xcm) -> Self { - VersionedXcm::V0(x) - } -} - -impl From> for VersionedXcm { - fn from(x: v1::Xcm) -> Self { - VersionedXcm::V1(x) - } -} - impl From> for VersionedXcm { fn from(x: v2::Xcm) -> Self { VersionedXcm::V2(x) } } -impl TryFrom> for v0::Xcm { - type Error = (); - fn try_from(x: VersionedXcm) -> Result { - use VersionedXcm::*; - match x { - V0(x) => Ok(x), - V1(x) => x.try_into(), - V2(x) => V1(x.try_into()?).try_into(), - } +impl From> for VersionedXcm { + fn from(x: v3::Xcm) -> Self { + VersionedXcm::V3(x) } } -impl TryFrom> for v1::Xcm { +impl TryFrom> for v2::Xcm { type Error = (); fn try_from(x: VersionedXcm) -> Result { use VersionedXcm::*; match x { - V0(x) => x.try_into(), - V1(x) => Ok(x), - V2(x) => x.try_into(), + V2(x) => Ok(x), + V3(x) => x.try_into(), } } } -impl TryFrom> for v2::Xcm { +impl TryFrom> for v3::Xcm { type Error = (); - fn try_from(x: VersionedXcm) -> Result { + fn try_from(x: VersionedXcm) -> Result { use VersionedXcm::*; match x { - V0(x) => V1(x.try_into()?).try_into(), - V1(x) => x.try_into(), - V2(x) => Ok(x), + V2(x) => x.try_into(), + V3(x) => Ok(x), } } } @@ -398,75 +423,65 @@ impl WrapVersion for () { } } -/// `WrapVersion` implementation which attempts to always convert the XCM to version 0 before wrapping it. -pub struct AlwaysV0; -impl WrapVersion for AlwaysV0 { - fn wrap_version( - _: &latest::MultiLocation, - xcm: impl Into>, - ) -> Result, ()> { - Ok(VersionedXcm::::V0(xcm.into().try_into()?)) - } -} - -/// `WrapVersion` implementation which attempts to always convert the XCM to version 1 before wrapping it. -pub struct AlwaysV1; -impl WrapVersion for AlwaysV1 { +/// `WrapVersion` implementation which attempts to always convert the XCM to version 2 before wrapping it. +pub struct AlwaysV2; +impl WrapVersion for AlwaysV2 { fn wrap_version( _: &latest::MultiLocation, xcm: impl Into>, ) -> Result, ()> { - Ok(VersionedXcm::::V1(xcm.into().try_into()?)) + Ok(VersionedXcm::::V2(xcm.into().try_into()?)) } } /// `WrapVersion` implementation which attempts to always convert the XCM to version 2 before wrapping it. -pub struct AlwaysV2; -impl WrapVersion for AlwaysV2 { - fn wrap_version( +pub struct AlwaysV3; +impl WrapVersion for AlwaysV3 { + fn wrap_version( _: &latest::MultiLocation, - xcm: impl Into>, - ) -> Result, ()> { - Ok(VersionedXcm::::V2(xcm.into().try_into()?)) + xcm: impl Into>, + ) -> Result, ()> { + Ok(VersionedXcm::::V3(xcm.into().try_into()?)) } } -/// `WrapVersion` implementation which attempts to always convert the XCM to the latest version before wrapping it. -pub type AlwaysLatest = AlwaysV1; +/// `WrapVersion` implementation which attempts to always convert the XCM to the latest version +/// before wrapping it. +pub type AlwaysLatest = AlwaysV3; -/// `WrapVersion` implementation which attempts to always convert the XCM to the release version before wrapping it. -pub type AlwaysRelease = AlwaysV0; +/// `WrapVersion` implementation which attempts to always convert the XCM to the most recent Long- +/// Term-Support version before wrapping it. +pub type AlwaysLts = AlwaysV3; pub mod prelude { pub use super::{ - latest::prelude::*, AlwaysLatest, AlwaysRelease, AlwaysV0, AlwaysV1, AlwaysV2, IntoVersion, - Unsupported, Version as XcmVersion, VersionedMultiAsset, VersionedMultiAssets, - VersionedMultiLocation, VersionedResponse, VersionedXcm, WrapVersion, + latest::prelude::*, AlwaysLatest, AlwaysLts, AlwaysV2, AlwaysV3, IntoVersion, Unsupported, + Version as XcmVersion, VersionedAssetId, VersionedInteriorMultiLocation, + VersionedMultiAsset, VersionedMultiAssets, VersionedMultiLocation, VersionedResponse, + VersionedXcm, WrapVersion, }; } pub mod opaque { - pub mod v0 { - // Everything from v0 - pub use crate::v0::*; - // Then override with the opaque types in v0 - pub use crate::v0::opaque::{Order, Xcm}; - } - pub mod v1 { - // Everything from v1 - pub use crate::v1::*; - // Then override with the opaque types in v1 - pub use crate::v1::opaque::{Order, Xcm}; - } pub mod v2 { - // Everything from v1 + // Everything from v2 pub use crate::v2::*; // Then override with the opaque types in v2 pub use crate::v2::opaque::{Instruction, Xcm}; } + pub mod v3 { + // Everything from v3 + pub use crate::v3::*; + // Then override with the opaque types in v3 + pub use crate::v3::opaque::{Instruction, Xcm}; + } pub mod latest { - pub use super::v2::*; + pub use super::v3::*; + } + + pub mod lts { + pub use super::v3::*; } /// The basic `VersionedXcm` type which just uses the `Vec` as an encoded call. @@ -477,3 +492,9 @@ pub mod opaque { pub trait GetWeight { fn weight(&self) -> latest::Weight; } + +#[test] +fn conversion_works() { + use latest::prelude::*; + let _: VersionedMultiAssets = (Here, 1u128).into(); +} diff --git a/xcm/src/v0/junction.rs b/xcm/src/v0/junction.rs deleted file mode 100644 index 450e882ac3e8..000000000000 --- a/xcm/src/v0/junction.rs +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot 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. - -// Polkadot 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 Polkadot. If not, see . - -//! Support data structures for `MultiLocation`, primarily the `Junction` datatype. - -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; -use sp_runtime::{traits::ConstU32, WeakBoundedVec}; - -/// A global identifier of an account-bearing consensus system. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] -pub enum NetworkId { - /// Unidentified/any. - Any, - /// Some named network. - Named(WeakBoundedVec>), - /// The Polkadot Relay chain - Polkadot, - /// Kusama. - Kusama, -} - -/// An identifier of a pluralistic body. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] -pub enum BodyId { - /// The only body in its context. - Unit, - /// A named body. - Named(WeakBoundedVec>), - /// An indexed body. - Index(#[codec(compact)] u32), - /// The unambiguous executive body (for Polkadot, this would be the Polkadot council). - Executive, - /// The unambiguous technical body (for Polkadot, this would be the Technical Committee). - Technical, - /// The unambiguous legislative body (for Polkadot, this could be considered the opinion of a majority of - /// lock-voters). - Legislative, - /// The unambiguous judicial body (this doesn't exist on Polkadot, but if it were to get a "grand oracle", it - /// may be considered as that). - Judicial, - /// The unambiguous defense body (for Polkadot, an opinion on the topic given via a public referendum - /// on the `staking_admin` track). - Defense, - /// The unambiguous administration body (for Polkadot, an opinion on the topic given via a public referendum - /// on the `general_admin` track). - Administration, - /// The unambiguous treasury body (for Polkadot, an opinion on the topic given via a public referendum - /// on the `treasurer` track). - Treasury, -} - -/// A part of a pluralistic body. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] -pub enum BodyPart { - /// The body's declaration, under whatever means it decides. - Voice, - /// A given number of members of the body. - Members { - #[codec(compact)] - count: u32, - }, - /// A given number of members of the body, out of some larger caucus. - Fraction { - #[codec(compact)] - nom: u32, - #[codec(compact)] - denom: u32, - }, - /// No less than the given proportion of members of the body. - AtLeastProportion { - #[codec(compact)] - nom: u32, - #[codec(compact)] - denom: u32, - }, - /// More than than the given proportion of members of the body. - MoreThanProportion { - #[codec(compact)] - nom: u32, - #[codec(compact)] - denom: u32, - }, -} - -impl BodyPart { - /// Returns `true` if the part represents a strict majority (> 50%) of the body in question. - pub fn is_majority(&self) -> bool { - match self { - BodyPart::Fraction { nom, denom } if *nom * 2 > *denom => true, - BodyPart::AtLeastProportion { nom, denom } if *nom * 2 > *denom => true, - BodyPart::MoreThanProportion { nom, denom } if *nom * 2 >= *denom => true, - _ => false, - } - } -} - -/// A single item in a path to describe the relative location of a consensus system. -/// -/// Each item assumes a pre-existing location as its context and is defined in terms of it. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] -pub enum Junction { - /// The consensus system of which the context is a member and state-wise super-set. - /// - /// NOTE: This item is *not* a sub-consensus item: a consensus system may not identify itself trustlessly as - /// a location that includes this junction. - Parent, - /// An indexed parachain belonging to and operated by the context. - /// - /// Generally used when the context is a Polkadot Relay-chain. - Parachain(#[codec(compact)] u32), - /// A 32-byte identifier for an account of a specific network that is respected as a sovereign endpoint within - /// the context. - /// - /// Generally used when the context is a Substrate-based chain. - AccountId32 { network: NetworkId, id: [u8; 32] }, - /// An 8-byte index for an account of a specific network that is respected as a sovereign endpoint within - /// the context. - /// - /// May be used when the context is a Frame-based chain and includes e.g. an indices pallet. - AccountIndex64 { - network: NetworkId, - #[codec(compact)] - index: u64, - }, - /// A 20-byte identifier for an account of a specific network that is respected as a sovereign endpoint within - /// the context. - /// - /// May be used when the context is an Ethereum or Bitcoin chain or smart-contract. - AccountKey20 { network: NetworkId, key: [u8; 20] }, - /// An instanced, indexed pallet that forms a constituent part of the context. - /// - /// Generally used when the context is a Frame-based chain. - PalletInstance(u8), - /// A non-descript index within the context location. - /// - /// Usage will vary widely owing to its generality. - /// - /// NOTE: Try to avoid using this and instead use a more specific item. - GeneralIndex(#[codec(compact)] u128), - /// A nondescript datum acting as a key within the context location. - /// - /// Usage will vary widely owing to its generality. - /// - /// NOTE: Try to avoid using this and instead use a more specific item. - GeneralKey(WeakBoundedVec>), - /// The unambiguous child. - /// - /// Not currently used except as a fallback when deriving ancestry. - OnlyChild, - /// A pluralistic body existing within consensus. - /// - /// Typical to be used to represent a governance origin of a chain, but could in principle be used to represent - /// things such as multisigs also. - Plurality { id: BodyId, part: BodyPart }, -} - -impl From for Junction { - fn from(v1: crate::v1::Junction) -> Junction { - use crate::v1::Junction::*; - match v1 { - Parachain(id) => Self::Parachain(id), - AccountId32 { network, id } => Self::AccountId32 { network, id }, - AccountIndex64 { network, index } => Self::AccountIndex64 { network, index }, - AccountKey20 { network, key } => Self::AccountKey20 { network, key }, - PalletInstance(index) => Self::PalletInstance(index), - GeneralIndex(index) => Self::GeneralIndex(index), - GeneralKey(key) => Self::GeneralKey(key), - OnlyChild => Self::OnlyChild, - Plurality { id, part } => Self::Plurality { id, part }, - } - } -} - -impl Junction { - /// Returns true if this junction is a `Parent` item. - pub fn is_parent(&self) -> bool { - match self { - Junction::Parent => true, - _ => false, - } - } - - /// Returns true if this junction can be considered an interior part of its context. This is generally `true`, - /// except for the `Parent` item. - pub fn is_interior(&self) -> bool { - match self { - Junction::Parent => false, - - Junction::Parachain(..) | - Junction::AccountId32 { .. } | - Junction::AccountIndex64 { .. } | - Junction::AccountKey20 { .. } | - Junction::PalletInstance { .. } | - Junction::GeneralIndex { .. } | - Junction::GeneralKey(..) | - Junction::OnlyChild | - Junction::Plurality { .. } => true, - } - } -} diff --git a/xcm/src/v0/mod.rs b/xcm/src/v0/mod.rs deleted file mode 100644 index 2a7bd3625bb0..000000000000 --- a/xcm/src/v0/mod.rs +++ /dev/null @@ -1,389 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot 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. - -// Polkadot 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 Polkadot. If not, see . - -//! Version 0 of the Cross-Consensus Message format data structures. - -use crate::DoubleEncoded; -use alloc::vec::Vec; -use core::result; -use derivative::Derivative; -use parity_scale_codec::{self, Decode, Encode}; -use scale_info::TypeInfo; - -mod junction; -mod multi_asset; -mod multi_location; -mod order; -mod traits; -use super::v1::{MultiLocation as MultiLocation1, Response as Response1, Xcm as Xcm1}; -pub use junction::{BodyId, BodyPart, Junction, NetworkId}; -pub use multi_asset::{AssetInstance, MultiAsset}; -pub use multi_location::MultiLocation::{self, *}; -pub use order::Order; -pub use traits::{Error, ExecuteXcm, Outcome, Result, SendXcm}; - -/// A prelude for importing all types typically used when interacting with XCM messages. -pub mod prelude { - pub use super::{ - junction::{BodyId, Junction::*}, - multi_asset::{ - AssetInstance::{self, *}, - MultiAsset::{self, *}, - }, - multi_location::MultiLocation::{self, *}, - order::Order::{self, *}, - traits::{Error as XcmError, ExecuteXcm, Outcome, Result as XcmResult, SendXcm}, - Junction::*, - OriginKind, - Xcm::{self, *}, - }; -} - -// TODO: #2841 #XCMENCODE Efficient encodings for MultiAssets, Vec, using initial byte values 128+ to encode -// the number of items in the vector. - -/// Basically just the XCM (more general) version of `ParachainDispatchOrigin`. -#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)] -pub enum OriginKind { - /// Origin should just be the native dispatch origin representation for the sender in the - /// local runtime framework. For Cumulus/Frame chains this is the `Parachain` or `Relay` origin - /// if coming from a chain, though there may be others if the `MultiLocation` XCM origin has a - /// primary/native dispatch origin form. - Native, - - /// Origin should just be the standard account-based origin with the sovereign account of - /// the sender. For Cumulus/Frame chains, this is the `Signed` origin. - SovereignAccount, - - /// Origin should be the super-user. For Cumulus/Frame chains, this is the `Root` origin. - /// This will not usually be an available option. - Superuser, - - /// Origin should be interpreted as an XCM native origin and the `MultiLocation` should be - /// encoded directly in the dispatch origin unchanged. For Cumulus/Frame chains, this will be - /// the `pallet_xcm::Origin::Xcm` type. - Xcm, -} - -/// Response data to a query. -#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)] -pub enum Response { - /// Some assets. - Assets(Vec), -} - -/// Cross-Consensus Message: A message from one consensus system to another. -/// -/// Consensus systems that may send and receive messages include blockchains and smart contracts. -/// -/// All messages are delivered from a known *origin*, expressed as a `MultiLocation`. -/// -/// This is the inner XCM format and is version-sensitive. Messages are typically passed using the outer -/// XCM format, known as `VersionedXcm`. -#[derive(Derivative, Encode, Decode, TypeInfo)] -#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] -#[codec(encode_bound())] -#[codec(decode_bound())] -#[scale_info(bounds(), skip_type_params(RuntimeCall))] -pub enum Xcm { - /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place them into `holding`. Execute the - /// orders (`effects`). - /// - /// - `assets`: The asset(s) to be withdrawn into holding. - /// - `effects`: The order(s) to execute on the holding account. - /// - /// Kind: *Instruction*. - /// - /// Errors: - #[codec(index = 0)] - WithdrawAsset { assets: Vec, effects: Vec> }, - - /// Asset(s) (`assets`) have been received into the ownership of this system on the `origin` system. - /// - /// Some orders are given (`effects`) which should be executed once the corresponding derivative assets have - /// been placed into `holding`. - /// - /// - `assets`: The asset(s) that are minted into holding. - /// - `effects`: The order(s) to execute on the holding account. - /// - /// Safety: `origin` must be trusted to have received and be storing `assets` such that they may later be - /// withdrawn should this system send a corresponding message. - /// - /// Kind: *Trusted Indication*. - /// - /// Errors: - #[codec(index = 1)] - ReserveAssetDeposit { assets: Vec, effects: Vec> }, - - /// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets should be - /// created on this system. - /// - /// Some orders are given (`effects`) which should be executed once the corresponding derivative assets have - /// been placed into `holding`. - /// - /// - `assets`: The asset(s) that are minted into holding. - /// - `effects`: The order(s) to execute on the holding account. - /// - /// Safety: `origin` must be trusted to have irrevocably destroyed the `assets` prior as a consequence of - /// sending this message. - /// - /// Kind: *Trusted Indication*. - /// - /// Errors: - #[codec(index = 2)] - TeleportAsset { assets: Vec, effects: Vec> }, - - /// Indication of the contents of the holding account corresponding to the `QueryHolding` order of `query_id`. - /// - /// - `query_id`: The identifier of the query that resulted in this message being sent. - /// - `assets`: The message content. - /// - /// Safety: No concerns. - /// - /// Kind: *Information*. - /// - /// Errors: - #[codec(index = 3)] - QueryResponse { - #[codec(compact)] - query_id: u64, - response: Response, - }, - - /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets under the - /// ownership of `dest` within this consensus system. - /// - /// - `assets`: The asset(s) to be withdrawn. - /// - `dest`: The new owner for the assets. - /// - /// Safety: No concerns. - /// - /// Kind: *Instruction*. - /// - /// Errors: - #[codec(index = 4)] - TransferAsset { assets: Vec, dest: MultiLocation }, - - /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets under the - /// ownership of `dest` within this consensus system. - /// - /// Send an onward XCM message to `dest` of `ReserveAssetDeposit` with the given `effects`. - /// - /// - `assets`: The asset(s) to be withdrawn. - /// - `dest`: The new owner for the assets. - /// - `effects`: The orders that should be contained in the `ReserveAssetDeposit` which is sent onwards to - /// `dest`. - /// - /// Safety: No concerns. - /// - /// Kind: *Instruction*. - /// - /// Errors: - #[codec(index = 5)] - TransferReserveAsset { assets: Vec, dest: MultiLocation, effects: Vec> }, - - /// Apply the encoded transaction `call`, whose dispatch-origin should be `origin` as expressed by the kind - /// of origin `origin_type`. - /// - /// - `origin_type`: The means of expressing the message origin as a dispatch origin. - /// - `max_weight`: The weight of `call`; this should be at least the chain's calculated weight and will - /// be used in the weight determination arithmetic. - /// - `call`: The encoded transaction to be applied. - /// - /// Safety: No concerns. - /// - /// Kind: *Instruction*. - /// - /// Errors: - #[codec(index = 6)] - Transact { - origin_type: OriginKind, - require_weight_at_most: u64, - call: DoubleEncoded, - }, - - /// A message to notify about a new incoming HRMP channel. This message is meant to be sent by the - /// relay-chain to a para. - /// - /// - `sender`: The sender in the to-be opened channel. Also, the initiator of the channel opening. - /// - `max_message_size`: The maximum size of a message proposed by the sender. - /// - `max_capacity`: The maximum number of messages that can be queued in the channel. - /// - /// Safety: The message should originate directly from the relay-chain. - /// - /// Kind: *System Notification* - #[codec(index = 7)] - HrmpNewChannelOpenRequest { - #[codec(compact)] - sender: u32, - #[codec(compact)] - max_message_size: u32, - #[codec(compact)] - max_capacity: u32, - }, - - /// A message to notify about that a previously sent open channel request has been accepted by - /// the recipient. That means that the channel will be opened during the next relay-chain session - /// change. This message is meant to be sent by the relay-chain to a para. - /// - /// Safety: The message should originate directly from the relay-chain. - /// - /// Kind: *System Notification* - /// - /// Errors: - #[codec(index = 8)] - HrmpChannelAccepted { - #[codec(compact)] - recipient: u32, - }, - - /// A message to notify that the other party in an open channel decided to close it. In particular, - /// `initiator` is going to close the channel opened from `sender` to the `recipient`. The close - /// will be enacted at the next relay-chain session change. This message is meant to be sent by - /// the relay-chain to a para. - /// - /// Safety: The message should originate directly from the relay-chain. - /// - /// Kind: *System Notification* - /// - /// Errors: - #[codec(index = 9)] - HrmpChannelClosing { - #[codec(compact)] - initiator: u32, - #[codec(compact)] - sender: u32, - #[codec(compact)] - recipient: u32, - }, - - /// A message to indicate that the embedded XCM is actually arriving on behalf of some consensus - /// location within the origin. - /// - /// Safety: `who` must be an interior location of the context. This basically means that no `Parent` - /// junctions are allowed in it. This should be verified at the time of XCM execution. - /// - /// Kind: *Instruction* - /// - /// Errors: - #[codec(index = 10)] - RelayedFrom { who: MultiLocation, message: alloc::boxed::Box> }, -} - -impl Xcm { - pub fn into(self) -> Xcm { - Xcm::from(self) - } - pub fn from(xcm: Xcm) -> Self { - use Xcm::*; - match xcm { - WithdrawAsset { assets, effects } => - WithdrawAsset { assets, effects: effects.into_iter().map(Order::into).collect() }, - ReserveAssetDeposit { assets, effects } => ReserveAssetDeposit { - assets, - effects: effects.into_iter().map(Order::into).collect(), - }, - TeleportAsset { assets, effects } => - TeleportAsset { assets, effects: effects.into_iter().map(Order::into).collect() }, - QueryResponse { query_id, response } => QueryResponse { query_id, response }, - TransferAsset { assets, dest } => TransferAsset { assets, dest }, - TransferReserveAsset { assets, dest, effects } => - TransferReserveAsset { assets, dest, effects }, - HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => - HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, - HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, - HrmpChannelClosing { initiator, sender, recipient } => - HrmpChannelClosing { initiator, sender, recipient }, - Transact { origin_type, require_weight_at_most, call } => - Transact { origin_type, require_weight_at_most, call: call.into() }, - RelayedFrom { who, message } => - RelayedFrom { who, message: alloc::boxed::Box::new((*message).into()) }, - } - } -} - -pub mod opaque { - /// The basic concrete type of `generic::Xcm`, which doesn't make any assumptions about the format of a - /// call other than it is pre-encoded. - pub type Xcm = super::Xcm<()>; - - pub use super::order::opaque::*; -} - -// Convert from a v1 response to a v0 response -impl TryFrom for Response { - type Error = (); - fn try_from(new_response: Response1) -> result::Result { - Ok(match new_response { - Response1::Assets(assets) => Self::Assets(assets.try_into()?), - Response1::Version(..) => return Err(()), - }) - } -} - -impl TryFrom> for Xcm { - type Error = (); - fn try_from(x: Xcm1) -> result::Result, ()> { - use Xcm::*; - Ok(match x { - Xcm1::WithdrawAsset { assets, effects } => WithdrawAsset { - assets: assets.try_into()?, - effects: effects - .into_iter() - .map(Order::try_from) - .collect::>()?, - }, - Xcm1::ReserveAssetDeposited { assets, effects } => ReserveAssetDeposit { - assets: assets.try_into()?, - effects: effects - .into_iter() - .map(Order::try_from) - .collect::>()?, - }, - Xcm1::ReceiveTeleportedAsset { assets, effects } => TeleportAsset { - assets: assets.try_into()?, - effects: effects - .into_iter() - .map(Order::try_from) - .collect::>()?, - }, - Xcm1::QueryResponse { query_id, response } => - QueryResponse { query_id, response: response.try_into()? }, - Xcm1::TransferAsset { assets, beneficiary } => - TransferAsset { assets: assets.try_into()?, dest: beneficiary.try_into()? }, - Xcm1::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { - assets: assets.try_into()?, - dest: dest.try_into()?, - effects: effects - .into_iter() - .map(Order::try_from) - .collect::>()?, - }, - Xcm1::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => - HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, - Xcm1::HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, - Xcm1::HrmpChannelClosing { initiator, sender, recipient } => - HrmpChannelClosing { initiator, sender, recipient }, - Xcm1::Transact { origin_type, require_weight_at_most, call } => - Transact { origin_type, require_weight_at_most, call: call.into() }, - Xcm1::RelayedFrom { who, message } => RelayedFrom { - who: MultiLocation1 { interior: who, parents: 0 }.try_into()?, - message: alloc::boxed::Box::new((*message).try_into()?), - }, - Xcm1::SubscribeVersion { .. } | Xcm1::UnsubscribeVersion => return Err(()), - }) - } -} diff --git a/xcm/src/v0/multi_asset.rs b/xcm/src/v0/multi_asset.rs deleted file mode 100644 index d8ad4ab8c2c9..000000000000 --- a/xcm/src/v0/multi_asset.rs +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot 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. - -// Polkadot 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 Polkadot. If not, see . - -//! Cross-Consensus Message format data structures. - -use super::MultiLocation; -use crate::v1::{MultiAssetFilter, MultiAssets, WildMultiAsset}; -use alloc::{vec, vec::Vec}; -use core::result; -use parity_scale_codec::{self, Decode, Encode}; -use scale_info::TypeInfo; - -pub use crate::v1::AssetInstance; - -/// A single general identifier for an asset. -/// -/// Represents both fungible and non-fungible assets. May only be used to represent a single asset class. -/// -/// Wildcards may or may not be allowed by the interpreting context. -/// -/// Assets classes may be identified in one of two ways: either an abstract identifier or a concrete identifier. -/// Implementations may support only one of these. A single asset may be referenced from multiple asset identifiers, -/// though will tend to have only a single *preferred* identifier. -/// -/// ### Abstract identifiers -/// -/// Abstract identifiers are absolute identifiers that represent a notional asset which can exist within multiple -/// consensus systems. These tend to be simpler to deal with since their broad meaning is unchanged regardless stay of -/// the consensus system in which it is interpreted. -/// -/// However, in the attempt to provide uniformity across consensus systems, they may conflate different instantiations -/// of some notional asset (e.g. the reserve asset and a local reserve-backed derivative of it) under the same name, -/// leading to confusion. It also implies that one notional asset is accounted for locally in only one way. This may not -/// be the case, e.g. where there are multiple bridge instances each providing a bridged "BTC" token yet none being -/// fungible between the others. -/// -/// Since they are meant to be absolute and universal, a global registry is needed to ensure that name collisions do not -/// occur. -/// -/// An abstract identifier is represented as a simple variable-size byte string. As of writing, no global registry -/// exists and no proposals have been put forth for asset labeling. -/// -/// ### Concrete identifiers -/// -/// Concrete identifiers are *relative identifiers* that specifically identify a single asset through its location in a -/// consensus system relative to the context interpreting. Use of a `MultiLocation` ensures that similar but non -/// fungible variants of the same underlying asset can be properly distinguished, and obviates the need for any kind of -/// central registry. -/// -/// The limitation is that the asset identifier cannot be trivially copied between consensus systems and must instead be -/// "re-anchored" whenever being moved to a new consensus system, using the two systems' relative paths. -/// -/// Throughout XCM, messages are authored such that *when interpreted from the receiver's point of view* they will have -/// the desired meaning/effect. This means that relative paths should always by constructed to be read from the point of -/// view of the receiving system, *which may be have a completely different meaning in the authoring system*. -/// -/// Concrete identifiers are the preferred way of identifying an asset since they are entirely unambiguous. -/// -/// A concrete identifier is represented by a `MultiLocation`. If a system has an unambiguous primary asset (such as -/// Bitcoin with BTC or Ethereum with ETH), then it will conventionally be identified as the chain itself. Alternative -/// and more specific ways of referring to an asset within a system include: -/// -/// - `/PalletInstance()` for a Frame chain with a single-asset pallet instance (such as an instance of the -/// Balances pallet). -/// - `/PalletInstance()/GeneralIndex()` for a Frame chain with an indexed multi-asset pallet instance -/// (such as an instance of the Assets pallet). -/// - `/AccountId32` for an ERC-20-style single-asset smart-contract on a Frame-based contracts chain. -/// - `/AccountKey20` for an ERC-20-style single-asset smart-contract on an Ethereum-like chain. -/// -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo)] -pub enum MultiAsset { - /// No assets. Rarely used. - None, - - /// All assets. Typically used for the subset of assets to be used for an `Order`, and in that context means - /// "all assets currently in holding". - All, - - /// All fungible assets. Typically used for the subset of assets to be used for an `Order`, and in that context - /// means "all fungible assets currently in holding". - AllFungible, - - /// All non-fungible assets. Typically used for the subset of assets to be used for an `Order`, and in that - /// context means "all non-fungible assets currently in holding". - AllNonFungible, - - /// All fungible assets of a given abstract asset `id`entifier. - AllAbstractFungible { id: Vec }, - - /// All non-fungible assets of a given abstract asset `class`. - AllAbstractNonFungible { class: Vec }, - - /// All fungible assets of a given concrete asset `id`entifier. - AllConcreteFungible { id: MultiLocation }, - - /// All non-fungible assets of a given concrete asset `class`. - AllConcreteNonFungible { class: MultiLocation }, - - /// Some specific `amount` of the fungible asset identified by an abstract `id`. - AbstractFungible { - id: Vec, - #[codec(compact)] - amount: u128, - }, - - /// Some specific `instance` of the non-fungible asset whose `class` is identified abstractly. - AbstractNonFungible { class: Vec, instance: AssetInstance }, - - /// Some specific `amount` of the fungible asset identified by an concrete `id`. - ConcreteFungible { - id: MultiLocation, - #[codec(compact)] - amount: u128, - }, - - /// Some specific `instance` of the non-fungible asset whose `class` is identified concretely. - ConcreteNonFungible { class: MultiLocation, instance: AssetInstance }, -} - -impl MultiAsset { - /// Returns `true` if the `MultiAsset` is a wildcard and can refer to classes of assets, instead of just one. - /// - /// Typically can also be inferred by the name starting with `All`. - pub fn is_wildcard(&self) -> bool { - match self { - MultiAsset::None | - MultiAsset::AbstractFungible { .. } | - MultiAsset::AbstractNonFungible { .. } | - MultiAsset::ConcreteFungible { .. } | - MultiAsset::ConcreteNonFungible { .. } => false, - - MultiAsset::All | - MultiAsset::AllFungible | - MultiAsset::AllNonFungible | - MultiAsset::AllAbstractFungible { .. } | - MultiAsset::AllConcreteFungible { .. } | - MultiAsset::AllAbstractNonFungible { .. } | - MultiAsset::AllConcreteNonFungible { .. } => true, - } - } - - fn is_none(&self) -> bool { - match self { - MultiAsset::None | - MultiAsset::AbstractFungible { amount: 0, .. } | - MultiAsset::ConcreteFungible { amount: 0, .. } => true, - - _ => false, - } - } - - fn is_fungible(&self) -> bool { - match self { - MultiAsset::All | - MultiAsset::AllFungible | - MultiAsset::AllAbstractFungible { .. } | - MultiAsset::AllConcreteFungible { .. } | - MultiAsset::AbstractFungible { .. } | - MultiAsset::ConcreteFungible { .. } => true, - - _ => false, - } - } - - fn is_non_fungible(&self) -> bool { - match self { - MultiAsset::All | - MultiAsset::AllNonFungible | - MultiAsset::AllAbstractNonFungible { .. } | - MultiAsset::AllConcreteNonFungible { .. } | - MultiAsset::AbstractNonFungible { .. } | - MultiAsset::ConcreteNonFungible { .. } => true, - - _ => false, - } - } - - fn is_concrete_fungible(&self, id: &MultiLocation) -> bool { - match self { - MultiAsset::AllFungible => true, - MultiAsset::AllConcreteFungible { id: i } | - MultiAsset::ConcreteFungible { id: i, .. } => i == id, - - _ => false, - } - } - - fn is_abstract_fungible(&self, id: &[u8]) -> bool { - match self { - MultiAsset::AllFungible => true, - MultiAsset::AllAbstractFungible { id: i } | - MultiAsset::AbstractFungible { id: i, .. } => i == id, - _ => false, - } - } - - fn is_concrete_non_fungible(&self, class: &MultiLocation) -> bool { - match self { - MultiAsset::AllNonFungible => true, - MultiAsset::AllConcreteNonFungible { class: i } | - MultiAsset::ConcreteNonFungible { class: i, .. } => i == class, - _ => false, - } - } - - fn is_abstract_non_fungible(&self, class: &[u8]) -> bool { - match self { - MultiAsset::AllNonFungible => true, - MultiAsset::AllAbstractNonFungible { class: i } | - MultiAsset::AbstractNonFungible { class: i, .. } => i == class, - _ => false, - } - } - - fn is_all(&self) -> bool { - matches!(self, MultiAsset::All) - } - - /// Returns true if `self` is a super-set of the given `inner`. - /// - /// Typically, any wildcard is never contained in anything else, and a wildcard can contain any other non-wildcard. - /// For more details, see the implementation and tests. - pub fn contains(&self, inner: &MultiAsset) -> bool { - use MultiAsset::*; - - // Inner cannot be wild - if inner.is_wildcard() { - return false - } - // Everything contains nothing. - if inner.is_none() { - return true - } - - // Everything contains anything. - if self.is_all() { - return true - } - // Nothing contains nothing. - if self.is_none() { - return false - } - - match self { - // Anything fungible contains "all fungibles" - AllFungible => inner.is_fungible(), - // Anything non-fungible contains "all non-fungibles" - AllNonFungible => inner.is_non_fungible(), - - AllConcreteFungible { id } => inner.is_concrete_fungible(id), - AllAbstractFungible { id } => inner.is_abstract_fungible(id), - AllConcreteNonFungible { class } => inner.is_concrete_non_fungible(class), - AllAbstractNonFungible { class } => inner.is_abstract_non_fungible(class), - - ConcreteFungible { id, amount } => matches!( - inner, - ConcreteFungible { id: inner_id , amount: inner_amount } if inner_id == id && amount >= inner_amount - ), - AbstractFungible { id, amount } => matches!( - inner, - AbstractFungible { id: inner_id , amount: inner_amount } if inner_id == id && amount >= inner_amount - ), - ConcreteNonFungible { .. } => self == inner, - AbstractNonFungible { .. } => self == inner, - _ => false, - } - } - - pub fn reanchor(&mut self, prepend: &MultiLocation) -> Result<(), ()> { - use MultiAsset::*; - match self { - AllConcreteFungible { ref mut id } | - AllConcreteNonFungible { class: ref mut id } | - ConcreteFungible { ref mut id, .. } | - ConcreteNonFungible { class: ref mut id, .. } => - id.prepend_with(prepend.clone()).map_err(|_| ()), - _ => Ok(()), - } - } -} - -impl TryFrom for MultiAsset { - type Error = (); - - fn try_from(m: crate::v1::MultiAsset) -> result::Result { - use crate::v1::{AssetId::*, Fungibility::*}; - use MultiAsset::*; - Ok(match (m.id, m.fun) { - (Concrete(id), Fungible(amount)) => ConcreteFungible { id: id.try_into()?, amount }, - (Concrete(class), NonFungible(instance)) => - ConcreteNonFungible { class: class.try_into()?, instance }, - (Abstract(id), Fungible(amount)) => AbstractFungible { id, amount }, - (Abstract(class), NonFungible(instance)) => AbstractNonFungible { class, instance }, - }) - } -} - -impl TryFrom for Vec { - type Error = (); - - fn try_from(m: MultiAssets) -> result::Result, ()> { - m.drain().into_iter().map(MultiAsset::try_from).collect() - } -} - -impl TryFrom for MultiAsset { - type Error = (); - - fn try_from(m: WildMultiAsset) -> result::Result { - use crate::v1::{AssetId::*, WildFungibility::*}; - use MultiAsset::*; - Ok(match m { - WildMultiAsset::All => All, - WildMultiAsset::AllOf { id, fun } => match (id, fun) { - (Concrete(id), Fungible) => AllConcreteFungible { id: id.try_into()? }, - (Concrete(class), NonFungible) => - AllConcreteNonFungible { class: class.try_into()? }, - (Abstract(id), Fungible) => AllAbstractFungible { id }, - (Abstract(class), NonFungible) => AllAbstractNonFungible { class }, - }, - }) - } -} - -impl TryFrom for Vec { - type Error = (); - - fn try_from(m: WildMultiAsset) -> result::Result, ()> { - Ok(vec![m.try_into()?]) - } -} - -impl TryFrom for Vec { - type Error = (); - - fn try_from(m: MultiAssetFilter) -> result::Result, ()> { - match m { - MultiAssetFilter::Definite(assets) => assets.try_into(), - MultiAssetFilter::Wild(wildcard) => wildcard.try_into(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn contains_works() { - use alloc::vec; - use MultiAsset::*; - // trivial case: all contains any non-wildcard. - assert!(All.contains(&None)); - assert!(All.contains(&AbstractFungible { id: alloc::vec![99u8], amount: 1 })); - - // trivial case: none contains nothing, except itself. - assert!(None.contains(&None)); - assert!(!None.contains(&AllFungible)); - assert!(!None.contains(&All)); - - // A bit more sneaky: Nothing can contain wildcard, even All ir the thing itself. - assert!(!All.contains(&All)); - assert!(!All.contains(&AllFungible)); - assert!(!AllFungible.contains(&AllFungible)); - assert!(!AllNonFungible.contains(&AllNonFungible)); - - // For fungibles, containing is basically equality, or equal id with higher amount. - assert!(!AbstractFungible { id: vec![99u8], amount: 99 } - .contains(&AbstractFungible { id: vec![1u8], amount: 99 })); - assert!(AbstractFungible { id: vec![99u8], amount: 99 } - .contains(&AbstractFungible { id: vec![99u8], amount: 99 })); - assert!(AbstractFungible { id: vec![99u8], amount: 99 } - .contains(&AbstractFungible { id: vec![99u8], amount: 9 })); - assert!(!AbstractFungible { id: vec![99u8], amount: 99 } - .contains(&AbstractFungible { id: vec![99u8], amount: 100 })); - - // For non-fungibles, containing is equality. - assert!(!AbstractNonFungible { class: vec![99u8], instance: AssetInstance::Index(9) } - .contains(&AbstractNonFungible { - class: vec![98u8], - instance: AssetInstance::Index(9) - })); - assert!(!AbstractNonFungible { class: vec![99u8], instance: AssetInstance::Index(8) } - .contains(&AbstractNonFungible { - class: vec![99u8], - instance: AssetInstance::Index(9) - })); - assert!(AbstractNonFungible { class: vec![99u8], instance: AssetInstance::Index(9) } - .contains(&AbstractNonFungible { - class: vec![99u8], - instance: AssetInstance::Index(9) - })); - } -} diff --git a/xcm/src/v0/multi_location.rs b/xcm/src/v0/multi_location.rs deleted file mode 100644 index 1bf49ad841a6..000000000000 --- a/xcm/src/v0/multi_location.rs +++ /dev/null @@ -1,745 +0,0 @@ -// Copyright 2020-2021 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot 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. - -// Polkadot 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 Polkadot. If not, see . - -//! Cross-Consensus Message format data structures. - -use super::Junction; -use core::{mem, result}; -use parity_scale_codec::{self, Decode, Encode}; - -/// A relative path between state-bearing consensus systems. -/// -/// A location in a consensus system is defined as an *isolatable state machine* held within global consensus. The -/// location in question need not have a sophisticated consensus algorithm of its own; a single account within -/// Ethereum, for example, could be considered a location. -/// -/// A very-much non-exhaustive list of types of location include: -/// - A (normal, layer-1) block chain, e.g. the Bitcoin mainnet or a parachain. -/// - A layer-0 super-chain, e.g. the Polkadot Relay chain. -/// - A layer-2 smart contract, e.g. an ERC-20 on Ethereum. -/// - A logical functional component of a chain, e.g. a single instance of a pallet on a Frame-based Substrate chain. -/// - An account. -/// -/// A `MultiLocation` is a *relative identifier*, meaning that it can only be used to define the relative path -/// between two locations, and cannot generally be used to refer to a location universally. It is comprised of a -/// number of *junctions*, each morphing the previous location, either diving down into one of its internal locations, -/// called a *sub-consensus*, or going up into its parent location. Correct `MultiLocation` values must have all -/// `Parent` junctions as a prefix to all *sub-consensus* junctions. -/// -/// This specific `MultiLocation` implementation uses a Rust `enum` in order to make pattern matching easier. -/// -/// The `MultiLocation` value of `Null` simply refers to the interpreting consensus system. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, scale_info::TypeInfo)] -pub enum MultiLocation { - /// The interpreting consensus system. - Null, - /// A relative path comprising 1 junction. - X1(Junction), - /// A relative path comprising 2 junctions. - X2(Junction, Junction), - /// A relative path comprising 3 junctions. - X3(Junction, Junction, Junction), - /// A relative path comprising 4 junctions. - X4(Junction, Junction, Junction, Junction), - /// A relative path comprising 5 junctions. - X5(Junction, Junction, Junction, Junction, Junction), - /// A relative path comprising 6 junctions. - X6(Junction, Junction, Junction, Junction, Junction, Junction), - /// A relative path comprising 7 junctions. - X7(Junction, Junction, Junction, Junction, Junction, Junction, Junction), - /// A relative path comprising 8 junctions. - X8(Junction, Junction, Junction, Junction, Junction, Junction, Junction, Junction), -} - -/// Maximum number of junctions a `MultiLocation` can contain. -pub const MAX_MULTILOCATION_LENGTH: usize = 8; - -xcm_procedural::impl_conversion_functions_for_multilocation_v0!(); - -pub struct MultiLocationIterator(MultiLocation); -impl Iterator for MultiLocationIterator { - type Item = Junction; - fn next(&mut self) -> Option { - self.0.take_first() - } -} - -pub struct MultiLocationReverseIterator(MultiLocation); -impl Iterator for MultiLocationReverseIterator { - type Item = Junction; - fn next(&mut self) -> Option { - self.0.take_last() - } -} - -pub struct MultiLocationRefIterator<'a>(&'a MultiLocation, usize); -impl<'a> Iterator for MultiLocationRefIterator<'a> { - type Item = &'a Junction; - fn next(&mut self) -> Option<&'a Junction> { - let result = self.0.at(self.1); - self.1 += 1; - result - } -} - -pub struct MultiLocationReverseRefIterator<'a>(&'a MultiLocation, usize); -impl<'a> Iterator for MultiLocationReverseRefIterator<'a> { - type Item = &'a Junction; - fn next(&mut self) -> Option<&'a Junction> { - self.1 += 1; - self.0.at(self.0.len().checked_sub(self.1)?) - } -} - -impl MultiLocation { - /// Returns first junction, or `None` if the location is empty. - pub fn first(&self) -> Option<&Junction> { - match &self { - MultiLocation::Null => None, - MultiLocation::X1(ref a) => Some(a), - MultiLocation::X2(ref a, ..) => Some(a), - MultiLocation::X3(ref a, ..) => Some(a), - MultiLocation::X4(ref a, ..) => Some(a), - MultiLocation::X5(ref a, ..) => Some(a), - MultiLocation::X6(ref a, ..) => Some(a), - MultiLocation::X7(ref a, ..) => Some(a), - MultiLocation::X8(ref a, ..) => Some(a), - } - } - - /// Returns last junction, or `None` if the location is empty. - pub fn last(&self) -> Option<&Junction> { - match &self { - MultiLocation::Null => None, - MultiLocation::X1(ref a) => Some(a), - MultiLocation::X2(.., ref a) => Some(a), - MultiLocation::X3(.., ref a) => Some(a), - MultiLocation::X4(.., ref a) => Some(a), - MultiLocation::X5(.., ref a) => Some(a), - MultiLocation::X6(.., ref a) => Some(a), - MultiLocation::X7(.., ref a) => Some(a), - MultiLocation::X8(.., ref a) => Some(a), - } - } - - /// Splits off the first junction, returning the remaining suffix (first item in tuple) and the first element - /// (second item in tuple) or `None` if it was empty. - pub fn split_first(self) -> (MultiLocation, Option) { - match self { - MultiLocation::Null => (MultiLocation::Null, None), - MultiLocation::X1(a) => (MultiLocation::Null, Some(a)), - MultiLocation::X2(a, b) => (MultiLocation::X1(b), Some(a)), - MultiLocation::X3(a, b, c) => (MultiLocation::X2(b, c), Some(a)), - MultiLocation::X4(a, b, c, d) => (MultiLocation::X3(b, c, d), Some(a)), - MultiLocation::X5(a, b, c, d, e) => (MultiLocation::X4(b, c, d, e), Some(a)), - MultiLocation::X6(a, b, c, d, e, f) => (MultiLocation::X5(b, c, d, e, f), Some(a)), - MultiLocation::X7(a, b, c, d, e, f, g) => - (MultiLocation::X6(b, c, d, e, f, g), Some(a)), - MultiLocation::X8(a, b, c, d, e, f, g, h) => - (MultiLocation::X7(b, c, d, e, f, g, h), Some(a)), - } - } - - /// Splits off the last junction, returning the remaining prefix (first item in tuple) and the last element - /// (second item in tuple) or `None` if it was empty. - pub fn split_last(self) -> (MultiLocation, Option) { - match self { - MultiLocation::Null => (MultiLocation::Null, None), - MultiLocation::X1(a) => (MultiLocation::Null, Some(a)), - MultiLocation::X2(a, b) => (MultiLocation::X1(a), Some(b)), - MultiLocation::X3(a, b, c) => (MultiLocation::X2(a, b), Some(c)), - MultiLocation::X4(a, b, c, d) => (MultiLocation::X3(a, b, c), Some(d)), - MultiLocation::X5(a, b, c, d, e) => (MultiLocation::X4(a, b, c, d), Some(e)), - MultiLocation::X6(a, b, c, d, e, f) => (MultiLocation::X5(a, b, c, d, e), Some(f)), - MultiLocation::X7(a, b, c, d, e, f, g) => - (MultiLocation::X6(a, b, c, d, e, f), Some(g)), - MultiLocation::X8(a, b, c, d, e, f, g, h) => - (MultiLocation::X7(a, b, c, d, e, f, g), Some(h)), - } - } - - /// Removes the first element from `self`, returning it (or `None` if it was empty). - pub fn take_first(&mut self) -> Option { - let mut d = MultiLocation::Null; - mem::swap(&mut *self, &mut d); - let (tail, head) = d.split_first(); - *self = tail; - head - } - - /// Removes the last element from `self`, returning it (or `None` if it was empty). - pub fn take_last(&mut self) -> Option { - let mut d = MultiLocation::Null; - mem::swap(&mut *self, &mut d); - let (head, tail) = d.split_last(); - *self = head; - tail - } - - /// Consumes `self` and returns a `MultiLocation` suffixed with `new`, or an `Err` with the original value of - /// `self` in case of overflow. - pub fn pushed_with(self, new: Junction) -> result::Result { - Ok(match self { - MultiLocation::Null => MultiLocation::X1(new), - MultiLocation::X1(a) => MultiLocation::X2(a, new), - MultiLocation::X2(a, b) => MultiLocation::X3(a, b, new), - MultiLocation::X3(a, b, c) => MultiLocation::X4(a, b, c, new), - MultiLocation::X4(a, b, c, d) => MultiLocation::X5(a, b, c, d, new), - MultiLocation::X5(a, b, c, d, e) => MultiLocation::X6(a, b, c, d, e, new), - MultiLocation::X6(a, b, c, d, e, f) => MultiLocation::X7(a, b, c, d, e, f, new), - MultiLocation::X7(a, b, c, d, e, f, g) => MultiLocation::X8(a, b, c, d, e, f, g, new), - s => Err(s)?, - }) - } - - /// Consumes `self` and returns a `MultiLocation` prefixed with `new`, or an `Err` with the original value of - /// `self` in case of overflow. - pub fn pushed_front_with(self, new: Junction) -> result::Result { - Ok(match self { - MultiLocation::Null => MultiLocation::X1(new), - MultiLocation::X1(a) => MultiLocation::X2(new, a), - MultiLocation::X2(a, b) => MultiLocation::X3(new, a, b), - MultiLocation::X3(a, b, c) => MultiLocation::X4(new, a, b, c), - MultiLocation::X4(a, b, c, d) => MultiLocation::X5(new, a, b, c, d), - MultiLocation::X5(a, b, c, d, e) => MultiLocation::X6(new, a, b, c, d, e), - MultiLocation::X6(a, b, c, d, e, f) => MultiLocation::X7(new, a, b, c, d, e, f), - MultiLocation::X7(a, b, c, d, e, f, g) => MultiLocation::X8(new, a, b, c, d, e, f, g), - s => Err(s)?, - }) - } - - /// Returns the number of junctions in `self`. - pub fn len(&self) -> usize { - match &self { - MultiLocation::Null => 0, - MultiLocation::X1(..) => 1, - MultiLocation::X2(..) => 2, - MultiLocation::X3(..) => 3, - MultiLocation::X4(..) => 4, - MultiLocation::X5(..) => 5, - MultiLocation::X6(..) => 6, - MultiLocation::X7(..) => 7, - MultiLocation::X8(..) => 8, - } - } - - /// Returns the junction at index `i`, or `None` if the location doesn't contain that many elements. - pub fn at(&self, i: usize) -> Option<&Junction> { - Some(match (i, &self) { - (0, MultiLocation::X1(ref a)) => a, - (0, MultiLocation::X2(ref a, ..)) => a, - (0, MultiLocation::X3(ref a, ..)) => a, - (0, MultiLocation::X4(ref a, ..)) => a, - (0, MultiLocation::X5(ref a, ..)) => a, - (0, MultiLocation::X6(ref a, ..)) => a, - (0, MultiLocation::X7(ref a, ..)) => a, - (0, MultiLocation::X8(ref a, ..)) => a, - (1, MultiLocation::X2(_, ref a)) => a, - (1, MultiLocation::X3(_, ref a, ..)) => a, - (1, MultiLocation::X4(_, ref a, ..)) => a, - (1, MultiLocation::X5(_, ref a, ..)) => a, - (1, MultiLocation::X6(_, ref a, ..)) => a, - (1, MultiLocation::X7(_, ref a, ..)) => a, - (1, MultiLocation::X8(_, ref a, ..)) => a, - (2, MultiLocation::X3(_, _, ref a)) => a, - (2, MultiLocation::X4(_, _, ref a, ..)) => a, - (2, MultiLocation::X5(_, _, ref a, ..)) => a, - (2, MultiLocation::X6(_, _, ref a, ..)) => a, - (2, MultiLocation::X7(_, _, ref a, ..)) => a, - (2, MultiLocation::X8(_, _, ref a, ..)) => a, - (3, MultiLocation::X4(_, _, _, ref a)) => a, - (3, MultiLocation::X5(_, _, _, ref a, ..)) => a, - (3, MultiLocation::X6(_, _, _, ref a, ..)) => a, - (3, MultiLocation::X7(_, _, _, ref a, ..)) => a, - (3, MultiLocation::X8(_, _, _, ref a, ..)) => a, - (4, MultiLocation::X5(_, _, _, _, ref a)) => a, - (4, MultiLocation::X6(_, _, _, _, ref a, ..)) => a, - (4, MultiLocation::X7(_, _, _, _, ref a, ..)) => a, - (4, MultiLocation::X8(_, _, _, _, ref a, ..)) => a, - (5, MultiLocation::X6(_, _, _, _, _, ref a)) => a, - (5, MultiLocation::X7(_, _, _, _, _, ref a, ..)) => a, - (5, MultiLocation::X8(_, _, _, _, _, ref a, ..)) => a, - (6, MultiLocation::X7(_, _, _, _, _, _, ref a)) => a, - (6, MultiLocation::X8(_, _, _, _, _, _, ref a, ..)) => a, - (7, MultiLocation::X8(_, _, _, _, _, _, _, ref a)) => a, - _ => return None, - }) - } - - /// Returns a mutable reference to the junction at index `i`, or `None` if the location doesn't contain that many - /// elements. - pub fn at_mut(&mut self, i: usize) -> Option<&mut Junction> { - Some(match (i, self) { - (0, MultiLocation::X1(ref mut a)) => a, - (0, MultiLocation::X2(ref mut a, ..)) => a, - (0, MultiLocation::X3(ref mut a, ..)) => a, - (0, MultiLocation::X4(ref mut a, ..)) => a, - (0, MultiLocation::X5(ref mut a, ..)) => a, - (0, MultiLocation::X6(ref mut a, ..)) => a, - (0, MultiLocation::X7(ref mut a, ..)) => a, - (0, MultiLocation::X8(ref mut a, ..)) => a, - (1, MultiLocation::X2(_, ref mut a)) => a, - (1, MultiLocation::X3(_, ref mut a, ..)) => a, - (1, MultiLocation::X4(_, ref mut a, ..)) => a, - (1, MultiLocation::X5(_, ref mut a, ..)) => a, - (1, MultiLocation::X6(_, ref mut a, ..)) => a, - (1, MultiLocation::X7(_, ref mut a, ..)) => a, - (1, MultiLocation::X8(_, ref mut a, ..)) => a, - (2, MultiLocation::X3(_, _, ref mut a)) => a, - (2, MultiLocation::X4(_, _, ref mut a, ..)) => a, - (2, MultiLocation::X5(_, _, ref mut a, ..)) => a, - (2, MultiLocation::X6(_, _, ref mut a, ..)) => a, - (2, MultiLocation::X7(_, _, ref mut a, ..)) => a, - (2, MultiLocation::X8(_, _, ref mut a, ..)) => a, - (3, MultiLocation::X4(_, _, _, ref mut a)) => a, - (3, MultiLocation::X5(_, _, _, ref mut a, ..)) => a, - (3, MultiLocation::X6(_, _, _, ref mut a, ..)) => a, - (3, MultiLocation::X7(_, _, _, ref mut a, ..)) => a, - (3, MultiLocation::X8(_, _, _, ref mut a, ..)) => a, - (4, MultiLocation::X5(_, _, _, _, ref mut a)) => a, - (4, MultiLocation::X6(_, _, _, _, ref mut a, ..)) => a, - (4, MultiLocation::X7(_, _, _, _, ref mut a, ..)) => a, - (4, MultiLocation::X8(_, _, _, _, ref mut a, ..)) => a, - (5, MultiLocation::X6(_, _, _, _, _, ref mut a)) => a, - (5, MultiLocation::X7(_, _, _, _, _, ref mut a, ..)) => a, - (5, MultiLocation::X8(_, _, _, _, _, ref mut a, ..)) => a, - (6, MultiLocation::X7(_, _, _, _, _, _, ref mut a)) => a, - (6, MultiLocation::X8(_, _, _, _, _, _, ref mut a, ..)) => a, - (7, MultiLocation::X8(_, _, _, _, _, _, _, ref mut a)) => a, - _ => return None, - }) - } - - /// Returns a reference iterator over the junctions. - pub fn iter(&self) -> MultiLocationRefIterator { - MultiLocationRefIterator(&self, 0) - } - - /// Returns a reference iterator over the junctions in reverse. - pub fn iter_rev(&self) -> MultiLocationReverseRefIterator { - MultiLocationReverseRefIterator(&self, 0) - } - - /// Consumes `self` and returns an iterator over the junctions. - pub fn into_iter(self) -> MultiLocationIterator { - MultiLocationIterator(self) - } - - /// Consumes `self` and returns an iterator over the junctions in reverse. - pub fn into_iter_rev(self) -> MultiLocationReverseIterator { - MultiLocationReverseIterator(self) - } - - /// Ensures that self begins with `prefix` and that it has a single `Junction` item following. - /// If so, returns a reference to this `Junction` item. - /// - /// # Example - /// ```rust - /// # use xcm::v0::{MultiLocation::*, Junction::*}; - /// # fn main() { - /// let mut m = X3(Parent, PalletInstance(3), OnlyChild); - /// assert_eq!(m.match_and_split(&X2(Parent, PalletInstance(3))), Some(&OnlyChild)); - /// assert_eq!(m.match_and_split(&X1(Parent)), None); - /// # } - /// ``` - pub fn match_and_split(&self, prefix: &MultiLocation) -> Option<&Junction> { - if prefix.len() + 1 != self.len() || !self.starts_with(prefix) { - return None - } - return self.at(prefix.len()) - } - - /// Returns whether `self` begins with or is equal to `prefix`. - /// - /// # Example - /// ```rust - /// # use xcm::v0::{Junction::*, MultiLocation::*}; - /// let m = X4(Parent, PalletInstance(3), OnlyChild, OnlyChild); - /// assert!(m.starts_with(&X2(Parent, PalletInstance(3)))); - /// assert!(m.starts_with(&m)); - /// assert!(!m.starts_with(&X2(Parent, GeneralIndex(99)))); - /// assert!(!m.starts_with(&X1(PalletInstance(3)))); - /// ``` - pub fn starts_with(&self, prefix: &MultiLocation) -> bool { - if self.len() < prefix.len() { - return false - } - prefix.iter().zip(self.iter()).all(|(l, r)| l == r) - } - - /// Mutates `self`, suffixing it with `new`. Returns `Err` in case of overflow. - pub fn push(&mut self, new: Junction) -> result::Result<(), ()> { - let mut n = MultiLocation::Null; - mem::swap(&mut *self, &mut n); - match n.pushed_with(new) { - Ok(result) => { - *self = result; - Ok(()) - }, - Err(old) => { - *self = old; - Err(()) - }, - } - } - - /// Mutates `self`, prefixing it with `new`. Returns `Err` in case of overflow. - pub fn push_front(&mut self, new: Junction) -> result::Result<(), ()> { - let mut n = MultiLocation::Null; - mem::swap(&mut *self, &mut n); - match n.pushed_front_with(new) { - Ok(result) => { - *self = result; - Ok(()) - }, - Err(old) => { - *self = old; - Err(()) - }, - } - } - - /// Returns the number of `Parent` junctions at the beginning of `self`. - pub fn leading_parent_count(&self) -> usize { - use Junction::Parent; - match self { - MultiLocation::X8(Parent, Parent, Parent, Parent, Parent, Parent, Parent, Parent) => 8, - - MultiLocation::X8(Parent, Parent, Parent, Parent, Parent, Parent, Parent, ..) => 7, - MultiLocation::X7(Parent, Parent, Parent, Parent, Parent, Parent, Parent) => 7, - - MultiLocation::X8(Parent, Parent, Parent, Parent, Parent, Parent, ..) => 6, - MultiLocation::X7(Parent, Parent, Parent, Parent, Parent, Parent, ..) => 6, - MultiLocation::X6(Parent, Parent, Parent, Parent, Parent, Parent) => 6, - - MultiLocation::X8(Parent, Parent, Parent, Parent, Parent, ..) => 5, - MultiLocation::X7(Parent, Parent, Parent, Parent, Parent, ..) => 5, - MultiLocation::X6(Parent, Parent, Parent, Parent, Parent, ..) => 5, - MultiLocation::X5(Parent, Parent, Parent, Parent, Parent) => 5, - - MultiLocation::X8(Parent, Parent, Parent, Parent, ..) => 4, - MultiLocation::X7(Parent, Parent, Parent, Parent, ..) => 4, - MultiLocation::X6(Parent, Parent, Parent, Parent, ..) => 4, - MultiLocation::X5(Parent, Parent, Parent, Parent, ..) => 4, - MultiLocation::X4(Parent, Parent, Parent, Parent) => 4, - - MultiLocation::X8(Parent, Parent, Parent, ..) => 3, - MultiLocation::X7(Parent, Parent, Parent, ..) => 3, - MultiLocation::X6(Parent, Parent, Parent, ..) => 3, - MultiLocation::X5(Parent, Parent, Parent, ..) => 3, - MultiLocation::X4(Parent, Parent, Parent, ..) => 3, - MultiLocation::X3(Parent, Parent, Parent) => 3, - - MultiLocation::X8(Parent, Parent, ..) => 2, - MultiLocation::X7(Parent, Parent, ..) => 2, - MultiLocation::X6(Parent, Parent, ..) => 2, - MultiLocation::X5(Parent, Parent, ..) => 2, - MultiLocation::X4(Parent, Parent, ..) => 2, - MultiLocation::X3(Parent, Parent, ..) => 2, - MultiLocation::X2(Parent, Parent) => 2, - - MultiLocation::X8(Parent, ..) => 1, - MultiLocation::X7(Parent, ..) => 1, - MultiLocation::X6(Parent, ..) => 1, - MultiLocation::X5(Parent, ..) => 1, - MultiLocation::X4(Parent, ..) => 1, - MultiLocation::X3(Parent, ..) => 1, - MultiLocation::X2(Parent, ..) => 1, - MultiLocation::X1(Parent) => 1, - _ => 0, - } - } - - /// This function ensures a multi-junction is in its canonicalized/normalized form, removing - /// any internal `[Non-Parent, Parent]` combinations. - pub fn canonicalize(&mut self) { - let mut normalized = MultiLocation::Null; - let mut iter = self.iter(); - // We build up the the new normalized path by taking items from the original multi-location. - // When the next item we would add is `Parent`, we instead remove the last item assuming - // it is non-parent. - const EXPECT_MESSAGE: &'static str = - "`self` is a well formed multi-location with N junctions; \ - this loop iterates over the junctions of `self`; \ - the loop can push to the new multi-location at most one time; \ - thus the size of the new multi-location is at most N junctions; \ - qed"; - while let Some(j) = iter.next() { - if j == &Junction::Parent { - match normalized.last() { - None | Some(Junction::Parent) => {}, - Some(_) => { - normalized.take_last(); - continue - }, - } - } - - normalized.push(j.clone()).expect(EXPECT_MESSAGE); - } - - core::mem::swap(self, &mut normalized); - } - - /// Mutate `self` so that it is suffixed with `suffix`. The correct normalized form is returned, - /// removing any internal `[Non-Parent, Parent]` combinations. - /// - /// In the case of overflow, `self` is unmodified and we return `Err` with `suffix`. - /// - /// # Example - /// ```rust - /// # use xcm::v0::{MultiLocation::*, Junction::*}; - /// # fn main() { - /// let mut m = X3(Parent, Parachain(21), OnlyChild); - /// assert_eq!(m.append_with(X2(Parent, PalletInstance(3))), Ok(())); - /// assert_eq!(m, X3(Parent, Parachain(21), PalletInstance(3))); - /// # } - /// ``` - pub fn append_with(&mut self, suffix: MultiLocation) -> Result<(), MultiLocation> { - let mut prefix = suffix; - core::mem::swap(self, &mut prefix); - match self.prepend_with(prefix) { - Ok(()) => Ok(()), - Err(prefix) => { - let mut suffix = prefix; - core::mem::swap(self, &mut suffix); - Err(suffix) - }, - } - } - - /// Mutate `self` so that it is prefixed with `prefix`. The correct normalized form is returned, - /// removing any internal [Non-Parent, `Parent`] combinations. - /// - /// In the case of overflow, `self` is unmodified and we return `Err` with `prefix`. - /// - /// # Example - /// ```rust - /// # use xcm::v0::{MultiLocation::*, Junction::*, NetworkId::Any}; - /// # fn main() { - /// let mut m = X3(Parent, Parent, PalletInstance(3)); - /// assert_eq!(m.prepend_with(X3(Parent, Parachain(21), OnlyChild)), Ok(())); - /// assert_eq!(m, X2(Parent, PalletInstance(3))); - /// # } - /// ``` - pub fn prepend_with(&mut self, prefix: MultiLocation) -> Result<(), MultiLocation> { - let mut prefix = prefix; - - // This will guarantee that all `Parent` junctions in the prefix are leading, which is - // important for calculating the `skipped` items below. - prefix.canonicalize(); - - let self_leading_parents = self.leading_parent_count(); - // These are the number of `non-parent` items in the prefix that we can - // potentially remove if the original location leads with parents. - let prefix_rest = prefix.len() - prefix.leading_parent_count(); - // 2 * skipped items will be removed when performing the normalization below. - let skipped = self_leading_parents.min(prefix_rest); - - // Pre-pending this prefix would create a multi-location with too many junctions. - if self.len() + prefix.len() - 2 * skipped > MAX_MULTILOCATION_LENGTH { - return Err(prefix) - } - - // Here we cancel out `[Non-Parent, Parent]` items (normalization), where - // the non-parent item comes from the end of the prefix, and the parent item - // comes from the front of the original location. - // - // We calculated already how many of these there should be above. - for _ in 0..skipped { - let _non_parent = prefix.take_last(); - let _parent = self.take_first(); - debug_assert!( - _non_parent.is_some() && _non_parent != Some(Junction::Parent), - "prepend_with should always remove a non-parent from the end of the prefix", - ); - debug_assert!( - _parent == Some(Junction::Parent), - "prepend_with should always remove a parent from the front of the location", - ); - } - - for j in prefix.into_iter_rev() { - self.push_front(j) - .expect("len + prefix minus 2*skipped is less than max length; qed"); - } - Ok(()) - } - - /// Returns true iff `self` is an interior location. For this it may not contain any `Junction`s - /// for which `Junction::is_interior` returns `false`. This is generally true, except for the - /// `Parent` item. - /// - /// # Example - /// ```rust - /// # use xcm::v0::{MultiLocation::*, Junction::*, NetworkId::Any}; - /// # fn main() { - /// let parent = X1(Parent); - /// assert_eq!(parent.is_interior(), false); - /// let m = X2(PalletInstance(12), AccountIndex64 { network: Any, index: 23 }); - /// assert_eq!(m.is_interior(), true); - /// # } - /// ``` - pub fn is_interior(&self) -> bool { - self.iter().all(Junction::is_interior) - } -} - -#[cfg(test)] -mod tests { - use super::MultiLocation::{self, *}; - use crate::opaque::v0::{Junction::*, NetworkId::Any}; - - #[test] - fn match_and_split_works() { - let m = X3(Parent, Parachain(42), AccountIndex64 { network: Any, index: 23 }); - assert_eq!(m.match_and_split(&X1(Parent)), None); - assert_eq!( - m.match_and_split(&X2(Parent, Parachain(42))), - Some(&AccountIndex64 { network: Any, index: 23 }) - ); - assert_eq!(m.match_and_split(&m), None); - } - - #[test] - fn starts_with_works() { - let full = X3(Parent, Parachain(1000), AccountIndex64 { network: Any, index: 23 }); - let identity = full.clone(); - let prefix = X2(Parent, Parachain(1000)); - let wrong_parachain = X2(Parent, Parachain(1001)); - let wrong_account = X3(Parent, Parachain(1000), AccountIndex64 { network: Any, index: 24 }); - let no_parents = X1(Parachain(1000)); - let too_many_parents = X3(Parent, Parent, Parachain(1000)); - - assert!(full.starts_with(&identity)); - assert!(full.starts_with(&prefix)); - assert!(!full.starts_with(&wrong_parachain)); - assert!(!full.starts_with(&wrong_account)); - assert!(!full.starts_with(&no_parents)); - assert!(!full.starts_with(&too_many_parents)); - } - - #[test] - fn append_with_works() { - let acc = AccountIndex64 { network: Any, index: 23 }; - let mut m = X2(Parent, Parachain(42)); - assert_eq!(m.append_with(X2(PalletInstance(3), acc.clone())), Ok(())); - assert_eq!(m, X4(Parent, Parachain(42), PalletInstance(3), acc.clone())); - - // cannot append to create overly long multilocation - let acc = AccountIndex64 { network: Any, index: 23 }; - let mut m = X7(Parent, Parent, Parent, Parent, Parent, Parent, Parachain(42)); - let suffix = X2(PalletInstance(3), acc.clone()); - assert_eq!(m.append_with(suffix.clone()), Err(suffix)); - } - - #[test] - fn prepend_with_works() { - let mut m = X3(Parent, Parachain(42), AccountIndex64 { network: Any, index: 23 }); - assert_eq!(m.prepend_with(X2(Parent, OnlyChild)), Ok(())); - assert_eq!(m, X3(Parent, Parachain(42), AccountIndex64 { network: Any, index: 23 })); - - // cannot prepend to create overly long multilocation - let mut m = X7(Parent, Parent, Parent, Parent, Parent, Parent, Parachain(42)); - let prefix = X2(Parent, Parent); - assert_eq!(m.prepend_with(prefix.clone()), Err(prefix)); - - // Can handle shared prefix and resizing correctly. - let mut m = X1(Parent); - let prefix = X8( - Parachain(100), - OnlyChild, - OnlyChild, - OnlyChild, - OnlyChild, - OnlyChild, - OnlyChild, - Parent, - ); - assert_eq!(m.prepend_with(prefix.clone()), Ok(())); - assert_eq!(m, X5(Parachain(100), OnlyChild, OnlyChild, OnlyChild, OnlyChild)); - - let mut m = X1(Parent); - let prefix = X8(Parent, Parent, Parent, Parent, Parent, Parent, Parent, Parent); - assert_eq!(m.prepend_with(prefix.clone()), Err(prefix)); - - let mut m = X1(Parent); - let prefix = X7(Parent, Parent, Parent, Parent, Parent, Parent, Parent); - assert_eq!(m.prepend_with(prefix.clone()), Ok(())); - assert_eq!(m, X8(Parent, Parent, Parent, Parent, Parent, Parent, Parent, Parent)); - - let mut m = X1(Parent); - let prefix = X8(Parent, Parent, Parent, Parent, OnlyChild, Parent, Parent, Parent); - assert_eq!(m.prepend_with(prefix.clone()), Ok(())); - assert_eq!(m, X7(Parent, Parent, Parent, Parent, Parent, Parent, Parent)); - } - - #[test] - fn canonicalize_works() { - let mut m = X1(Parent); - m.canonicalize(); - assert_eq!(m, X1(Parent)); - - let mut m = X1(Parachain(1)); - m.canonicalize(); - assert_eq!(m, X1(Parachain(1))); - - let mut m = X6(Parent, Parachain(1), Parent, Parachain(2), Parent, Parachain(3)); - m.canonicalize(); - assert_eq!(m, X2(Parent, Parachain(3))); - - let mut m = X5(Parachain(1), Parent, Parachain(2), Parent, Parachain(3)); - m.canonicalize(); - assert_eq!(m, X1(Parachain(3))); - - let mut m = X6(Parachain(1), Parent, Parachain(2), Parent, Parachain(3), Parent); - m.canonicalize(); - assert_eq!(m, Null); - - let mut m = X5(Parachain(1), Parent, Parent, Parent, Parachain(3)); - m.canonicalize(); - assert_eq!(m, X3(Parent, Parent, Parachain(3))); - - let mut m = X4(Parachain(1), Parachain(2), Parent, Parent); - m.canonicalize(); - assert_eq!(m, Null); - - let mut m = X4(Parent, Parent, Parachain(1), Parachain(2)); - m.canonicalize(); - assert_eq!(m, X4(Parent, Parent, Parachain(1), Parachain(2))); - } - - #[test] - fn conversion_from_other_types_works() { - use crate::v1::{self, Junction, Junctions}; - - fn takes_multilocation>(_arg: Arg) {} - - takes_multilocation(Null); - takes_multilocation(Parent); - takes_multilocation([Parent, Parachain(4)]); - - assert_eq!(v1::MultiLocation::here().try_into(), Ok(MultiLocation::Null)); - assert_eq!( - v1::MultiLocation::new(1, Junctions::X1(Junction::Parachain(8))).try_into(), - Ok(X2(Parent, Parachain(8))), - ); - assert_eq!( - v1::MultiLocation::new(24, Junctions::Here).try_into(), - Err::(()), - ); - } -} diff --git a/xcm/src/v0/order.rs b/xcm/src/v0/order.rs deleted file mode 100644 index 306610c28c9c..000000000000 --- a/xcm/src/v0/order.rs +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot 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. - -// Polkadot 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 Polkadot. If not, see . - -//! Version 0 of the Cross-Consensus Message format data structures. - -use super::{super::v1::Order as Order1, MultiAsset, MultiLocation, Xcm}; -use alloc::vec::Vec; -use core::result; -use derivative::Derivative; -use parity_scale_codec::{self, Decode, Encode}; - -/// An instruction to be executed on some or all of the assets in holding, used by asset-related XCM messages. -#[derive(Derivative, Encode, Decode, scale_info::TypeInfo)] -#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] -#[codec(encode_bound())] -#[codec(decode_bound())] -#[scale_info(bounds(), skip_type_params(RuntimeCall))] -pub enum Order { - /// Do nothing. Not generally used. - #[codec(index = 0)] - Null, - - /// Remove the asset(s) (`assets`) from holding and place equivalent assets under the ownership of `dest` within - /// this consensus system. - /// - /// - `assets`: The asset(s) to remove from holding. - /// - `dest`: The new owner for the assets. - /// - /// Errors: - #[codec(index = 1)] - DepositAsset { assets: Vec, dest: MultiLocation }, - - /// Remove the asset(s) (`assets`) from holding and place equivalent assets under the ownership of `dest` within - /// this consensus system. - /// - /// Send an onward XCM message to `dest` of `ReserveAssetDeposit` with the given `effects`. - /// - /// - `assets`: The asset(s) to remove from holding. - /// - `dest`: The new owner for the assets. - /// - `effects`: The orders that should be contained in the `ReserveAssetDeposit` which is sent onwards to - /// `dest`. - /// - /// Errors: - #[codec(index = 2)] - DepositReserveAsset { assets: Vec, dest: MultiLocation, effects: Vec> }, - - /// Remove the asset(s) (`give`) from holding and replace them with alternative assets. - /// - /// The minimum amount of assets to be received into holding for the order not to fail may be stated. - /// - /// - `give`: The asset(s) to remove from holding. - /// - `receive`: The minimum amount of assets(s) which `give` should be exchanged for. The meaning of wildcards - /// is undefined and they should be not be used. - /// - /// Errors: - #[codec(index = 3)] - ExchangeAsset { give: Vec, receive: Vec }, - - /// Remove the asset(s) (`assets`) from holding and send a `WithdrawAsset` XCM message to a reserve location. - /// - /// - `assets`: The asset(s) to remove from holding. - /// - `reserve`: A valid location that acts as a reserve for all asset(s) in `assets`. The sovereign account - /// of this consensus system *on the reserve location* will have appropriate assets withdrawn and `effects` will - /// be executed on them. There will typically be only one valid location on any given asset/chain combination. - /// - `effects`: The orders to execute on the assets once withdrawn *on the reserve location*. - /// - /// Errors: - #[codec(index = 4)] - InitiateReserveWithdraw { - assets: Vec, - reserve: MultiLocation, - effects: Vec>, - }, - - /// Remove the asset(s) (`assets`) from holding and send a `TeleportAsset` XCM message to a destination location. - /// - /// - `assets`: The asset(s) to remove from holding. - /// - `destination`: A valid location that has a bi-lateral teleportation arrangement. - /// - `effects`: The orders to execute on the assets once arrived *on the destination location*. - /// - /// Errors: - #[codec(index = 5)] - InitiateTeleport { assets: Vec, dest: MultiLocation, effects: Vec> }, - - /// Send a `Balances` XCM message with the `assets` value equal to the holding contents, or a portion thereof. - /// - /// - `query_id`: An identifier that will be replicated into the returned XCM message. - /// - `dest`: A valid destination for the returned XCM message. This may be limited to the current origin. - /// - `assets`: A filter for the assets that should be reported back. The assets reported back will be, asset- - /// wise, *the lesser of this value and the holding account*. No wildcards will be used when reporting assets - /// back. - /// - /// Errors: - #[codec(index = 6)] - QueryHolding { - #[codec(compact)] - query_id: u64, - dest: MultiLocation, - assets: Vec, - }, - - /// Pay for the execution of some XCM with up to `weight` picoseconds of execution time, paying for this with - /// up to `fees` from the holding account. - /// - /// Errors: - #[codec(index = 7)] - BuyExecution { - fees: MultiAsset, - weight: u64, - debt: u64, - halt_on_error: bool, - xcm: Vec>, - }, -} - -pub mod opaque { - pub type Order = super::Order<()>; -} - -impl Order { - pub fn into(self) -> Order { - Order::from(self) - } - pub fn from(order: Order) -> Self { - use Order::*; - match order { - Null => Null, - DepositAsset { assets, dest } => DepositAsset { assets, dest }, - DepositReserveAsset { assets, dest, effects } => - DepositReserveAsset { assets, dest, effects }, - ExchangeAsset { give, receive } => ExchangeAsset { give, receive }, - InitiateReserveWithdraw { assets, reserve, effects } => - InitiateReserveWithdraw { assets, reserve, effects }, - InitiateTeleport { assets, dest, effects } => - InitiateTeleport { assets, dest, effects }, - QueryHolding { query_id, dest, assets } => QueryHolding { query_id, dest, assets }, - BuyExecution { fees, weight, debt, halt_on_error, xcm } => { - let xcm = xcm.into_iter().map(Xcm::from).collect(); - BuyExecution { fees, weight, debt, halt_on_error, xcm } - }, - } - } -} - -impl TryFrom> for Order { - type Error = (); - fn try_from(old: Order1) -> result::Result, ()> { - use Order::*; - Ok(match old { - Order1::Noop => Null, - Order1::DepositAsset { assets, beneficiary, .. } => - DepositAsset { assets: assets.try_into()?, dest: beneficiary.try_into()? }, - Order1::DepositReserveAsset { assets, dest, effects, .. } => DepositReserveAsset { - assets: assets.try_into()?, - dest: dest.try_into()?, - effects: effects - .into_iter() - .map(Order::<()>::try_from) - .collect::>()?, - }, - Order1::ExchangeAsset { give, receive } => - ExchangeAsset { give: give.try_into()?, receive: receive.try_into()? }, - Order1::InitiateReserveWithdraw { assets, reserve, effects } => - InitiateReserveWithdraw { - assets: assets.try_into()?, - reserve: reserve.try_into()?, - effects: effects - .into_iter() - .map(Order::<()>::try_from) - .collect::>()?, - }, - Order1::InitiateTeleport { assets, dest, effects } => InitiateTeleport { - assets: assets.try_into()?, - dest: dest.try_into()?, - effects: effects - .into_iter() - .map(Order::<()>::try_from) - .collect::>()?, - }, - Order1::QueryHolding { query_id, dest, assets } => - QueryHolding { query_id, dest: dest.try_into()?, assets: assets.try_into()? }, - Order1::BuyExecution { fees, weight, debt, halt_on_error, instructions } => { - let xcm = instructions - .into_iter() - .map(Xcm::::try_from) - .collect::>()?; - BuyExecution { fees: fees.try_into()?, weight, debt, halt_on_error, xcm } - }, - }) - } -} diff --git a/xcm/src/v0/traits.rs b/xcm/src/v0/traits.rs deleted file mode 100644 index ace93ef64ff5..000000000000 --- a/xcm/src/v0/traits.rs +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot 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. - -// Polkadot 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 Polkadot. If not, see . - -//! Cross-Consensus Message format data structures. - -use core::result; -use parity_scale_codec::{Decode, Encode}; - -use super::{MultiLocation, Xcm}; - -#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, scale_info::TypeInfo)] -pub enum Error { - Undefined, - /// An arithmetic overflow happened. - Overflow, - /// The operation is intentionally unsupported. - Unimplemented, - UnhandledXcmVersion, - /// The implementation does not handle a given XCM. - UnhandledXcmMessage, - /// The implementation does not handle an effect present in an XCM. - UnhandledEffect, - EscalationOfPrivilege, - UntrustedReserveLocation, - UntrustedTeleportLocation, - DestinationBufferOverflow, - /// The message and destination was recognized as being reachable but the operation could not be completed. - /// A human-readable explanation of the specific issue is provided. - SendFailed(#[codec(skip)] &'static str), - /// The message and destination combination was not recognized as being reachable. - CannotReachDestination(MultiLocation, Xcm<()>), - MultiLocationFull, - FailedToDecode, - BadOrigin, - ExceedsMaxMessageSize, - /// An asset transaction (like withdraw or deposit) failed. - /// See implementers of the `TransactAsset` trait for sources. - /// Causes can include type conversion failures between id or balance types. - FailedToTransactAsset(#[codec(skip)] &'static str), - /// Execution of the XCM would potentially result in a greater weight used than the pre-specified - /// weight limit. The amount that is potentially required is the parameter. - WeightLimitReached(Weight), - /// An asset wildcard was passed where it was not expected (e.g. as the asset to withdraw in a - /// `WithdrawAsset` XCM). - Wildcard, - /// The case where an XCM message has specified a weight limit on an interior call and this - /// limit is too low. - /// - /// Used by: - /// - `Transact` - MaxWeightInvalid, - /// The fees specified by the XCM message were not found in the holding account. - /// - /// Used by: - /// - `BuyExecution` - NotHoldingFees, - /// The weight of an XCM message is not computable ahead of execution. This generally means at least part - /// of the message is invalid, which could be due to it containing overly nested structures or an invalid - /// nested data segment (e.g. for the call in `Transact`). - WeightNotComputable, - /// The XCM did not pass the barrier condition for execution. The barrier condition differs on different - /// chains and in different circumstances, but generally it means that the conditions surrounding the message - /// were not such that the chain considers the message worth spending time executing. Since most chains - /// lift the barrier to execution on appropriate payment, presentation of an NFT voucher, or based on the - /// message origin, it means that none of those were the case. - Barrier, - /// Indicates that it is not possible for a location to have an asset be withdrawn or transferred from its - /// ownership. This probably means it doesn't own (enough of) it, but may also indicate that it is under a - /// lock, hold, freeze or is otherwise unavailable. - NotWithdrawable, - /// Indicates that the consensus system cannot deposit an asset under the ownership of a particular location. - LocationCannotHold, - /// The assets given to purchase weight is are insufficient for the weight desired. - TooExpensive, - /// The given asset is not handled. - AssetNotFound, - /// `execute_xcm` has been called too many times recursively. - RecursionLimitReached, -} - -impl From<()> for Error { - fn from(_: ()) -> Self { - Self::Undefined - } -} - -pub type Result = result::Result<(), Error>; - -/// Local weight type; execution time in picoseconds. -pub type Weight = u64; - -/// Outcome of an XCM execution. -#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, scale_info::TypeInfo)] -pub enum Outcome { - /// Execution completed successfully; given weight was used. - Complete(Weight), - /// Execution started, but did not complete successfully due to the given error; given weight was used. - Incomplete(Weight, Error), - /// Execution did not start due to the given error. - Error(Error), -} - -impl Outcome { - pub fn ensure_complete(self) -> Result { - match self { - Outcome::Complete(_) => Ok(()), - Outcome::Incomplete(_, e) => Err(e), - Outcome::Error(e) => Err(e), - } - } - pub fn ensure_execution(self) -> result::Result { - match self { - Outcome::Complete(w) => Ok(w), - Outcome::Incomplete(w, _) => Ok(w), - Outcome::Error(e) => Err(e), - } - } - /// How much weight was used by the XCM execution attempt. - pub fn weight_used(&self) -> Weight { - match self { - Outcome::Complete(w) => *w, - Outcome::Incomplete(w, _) => *w, - Outcome::Error(_) => 0, - } - } -} - -/// Type of XCM message executor. -pub trait ExecuteXcm { - /// Execute some XCM `message` from `origin` using no more than `weight_limit` weight. The weight limit is - /// a basic hard-limit and the implementation may place further restrictions or requirements on weight and - /// other aspects. - fn execute_xcm( - origin: MultiLocation, - message: Xcm, - weight_limit: Weight, - ) -> Outcome { - log::debug!( - target: "xcm::execute_xcm", - "origin: {:?}, message: {:?}, weight_limit: {:?}", - origin, - message, - weight_limit, - ); - Self::execute_xcm_in_credit(origin, message, weight_limit, 0) - } - - /// Execute some XCM `message` from `origin` using no more than `weight_limit` weight. - /// - /// Some amount of `weight_credit` may be provided which, depending on the implementation, may allow - /// execution without associated payment. - fn execute_xcm_in_credit( - origin: MultiLocation, - message: Xcm, - weight_limit: Weight, - weight_credit: Weight, - ) -> Outcome; -} - -impl ExecuteXcm for () { - fn execute_xcm_in_credit( - _origin: MultiLocation, - _message: Xcm, - _weight_limit: Weight, - _weight_credit: Weight, - ) -> Outcome { - Outcome::Error(Error::Unimplemented) - } -} - -/// Utility for sending an XCM message. -/// -/// These can be amalgamated in tuples to form sophisticated routing systems. In tuple format, each router might return -/// `CannotReachDestination` to pass the execution to the next sender item. Note that each `CannotReachDestination` -/// might alter the destination and the XCM message for to the next router. -/// -/// -/// # Example -/// ```rust -/// # use xcm::v0::{MultiLocation, Xcm, Junction, Error, OriginKind, SendXcm, Result}; -/// # use parity_scale_codec::Encode; -/// -/// /// A sender that only passes the message through and does nothing. -/// struct Sender1; -/// impl SendXcm for Sender1 { -/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { -/// return Err(Error::CannotReachDestination(destination, message)) -/// } -/// } -/// -/// /// A sender that accepts a message that has an X2 junction, otherwise stops the routing. -/// struct Sender2; -/// impl SendXcm for Sender2 { -/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { -/// if let MultiLocation::X2(j1, j2) = destination { -/// Ok(()) -/// } else { -/// Err(Error::Undefined) -/// } -/// } -/// } -/// -/// /// A sender that accepts a message from an X1 parent junction, passing through otherwise. -/// struct Sender3; -/// impl SendXcm for Sender3 { -/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { -/// match destination { -/// MultiLocation::X1(j) if j == Junction::Parent => Ok(()), -/// _ => Err(Error::CannotReachDestination(destination, message)), -/// } -/// } -/// } -/// -/// // A call to send via XCM. We don't really care about this. -/// # fn main() { -/// let call: Vec = ().encode(); -/// let message = Xcm::Transact { origin_type: OriginKind::Superuser, require_weight_at_most: 0, call: call.into() }; -/// let destination = MultiLocation::X1(Junction::Parent); -/// -/// assert!( -/// // Sender2 will block this. -/// <(Sender1, Sender2, Sender3) as SendXcm>::send_xcm(destination.clone(), message.clone()) -/// .is_err() -/// ); -/// -/// assert!( -/// // Sender3 will catch this. -/// <(Sender1, Sender3) as SendXcm>::send_xcm(destination.clone(), message.clone()) -/// .is_ok() -/// ); -/// # } -/// ``` -pub trait SendXcm { - /// Send an XCM `message` to a given `destination`. - /// - /// If it is not a destination which can be reached with this type but possibly could by others, then it *MUST* - /// return `CannotReachDestination`. Any other error will cause the tuple implementation to exit early without - /// trying other type fields. - fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result; -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl SendXcm for Tuple { - fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { - for_tuples!( #( - // we shadow `destination` and `message` in each expansion for the next one. - let (destination, message) = match Tuple::send_xcm(destination, message) { - Err(Error::CannotReachDestination(d, m)) => (d, m), - o @ _ => return o, - }; - )* ); - Err(Error::CannotReachDestination(destination, message)) - } -} diff --git a/xcm/src/v1/mod.rs b/xcm/src/v1/mod.rs deleted file mode 100644 index 7703425149a4..000000000000 --- a/xcm/src/v1/mod.rs +++ /dev/null @@ -1,518 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot 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. - -// Polkadot 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 Polkadot. If not, see . - -//! # XCM Version 1 -//! Version 1 of the Cross-Consensus Message format data structures. The comprehensive list of -//! changes can be found in -//! [this PR description](https://github.com/paritytech/polkadot/pull/2815#issue-608567900). -//! -//! ## Changes to be aware of -//! Most changes should automatically be resolved via the conversion traits (i.e. `TryFrom` and -//! `From`). The list here is mostly for incompatible changes that result in an `Err(())` when -//! attempting to convert XCM objects from v0. -//! -//! ### Junction -//! - `v0::Junction::Parent` cannot be converted to v1, because the way we represent parents in v1 -//! has changed - instead of being a property of the junction, v1 `MultiLocation`s now have an -//! extra field representing the number of parents that the `MultiLocation` contains. -//! -//! ### `MultiLocation` -//! - The `try_from` conversion method will always canonicalize the v0 `MultiLocation` before -//! attempting to do the proper conversion. Since canonicalization is not a fallible operation, -//! we do not expect v0 `MultiLocation` to ever fail to be upgraded to v1. -//! -//! ### `MultiAsset` -//! - Stronger typing to differentiate between a single class of `MultiAsset` and several classes -//! of `MultiAssets` is introduced. As the name suggests, a `Vec` that is used on all -//! APIs will instead be using a new type called `MultiAssets` (note the `s`). -//! - All `MultiAsset` variants whose name contains "All" in it, namely `v0::MultiAsset::All`, -//! `v0::MultiAsset::AllFungible`, `v0::MultiAsset::AllNonFungible`, -//! `v0::MultiAsset::AllAbstractFungible`, `v0::MultiAsset::AllAbstractNonFungible`, -//! `v0::MultiAsset::AllConcreteFungible` and `v0::MultiAsset::AllConcreteNonFungible`, will fail -//! to convert to v1 `MultiAsset`, since v1 does not contain these variants. -//! - Similarly, all `MultiAsset` variants whose name contains "All" in it can be converted into a -//! `WildMultiAsset`. -//! - `v0::MultiAsset::None` is not represented at all in v1. -//! -//! ### XCM -//! - No special attention necessary -//! -//! ### Order -//! - `v1::Order::DepositAsset` and `v1::Order::DepositReserveAsset` both introduced a new -//! `max_asset` field that limits the maximum classes of assets that can be deposited. During -//! conversion from v0, the `max_asset` field defaults to 1. -//! - v1 Orders that contain `MultiAsset` as argument(s) will need to explicitly specify the amount -//! and details of assets. This is to prevent accidental misuse of `All` to possibly transfer, -//! spend or otherwise perform unintended operations on `All` assets. -//! - v1 Orders that do allow the notion of `All` to be used as wildcards, will instead use a new -//! type called `MultiAssetFilter`. - -use super::{ - v0::{Response as OldResponse, Xcm as OldXcm}, - v2::{Instruction, Response as NewResponse, Xcm as NewXcm}, -}; -use crate::DoubleEncoded; -use alloc::vec::Vec; -use core::{fmt::Debug, result}; -use derivative::Derivative; -use parity_scale_codec::{self, Decode, Encode}; -use scale_info::TypeInfo; - -mod junction; -mod multiasset; -mod multilocation; -mod order; -mod traits; // the new multiasset. - -pub use junction::Junction; -pub use multiasset::{ - AssetId, AssetInstance, Fungibility, MultiAsset, MultiAssetFilter, MultiAssets, - WildFungibility, WildMultiAsset, -}; -pub use multilocation::{ - Ancestor, AncestorThen, InteriorMultiLocation, Junctions, MultiLocation, Parent, ParentThen, -}; -pub use order::Order; -pub use traits::{Error, ExecuteXcm, Outcome, Result, SendXcm}; - -// These parts of XCM v0 have been unchanged in XCM v1, and are re-imported here. -pub use super::v0::{BodyId, BodyPart, NetworkId, OriginKind}; - -/// A prelude for importing all types typically used when interacting with XCM messages. -pub mod prelude { - pub use super::{ - junction::Junction::{self, *}, - opaque, - order::Order::{self, *}, - Ancestor, AncestorThen, - AssetId::{self, *}, - AssetInstance::{self, *}, - BodyId, BodyPart, Error as XcmError, ExecuteXcm, - Fungibility::{self, *}, - InteriorMultiLocation, - Junctions::{self, *}, - MultiAsset, - MultiAssetFilter::{self, *}, - MultiAssets, MultiLocation, - NetworkId::{self, *}, - OriginKind, Outcome, Parent, ParentThen, Response, Result as XcmResult, SendXcm, - WildFungibility::{self, Fungible as WildFungible, NonFungible as WildNonFungible}, - WildMultiAsset::{self, *}, - Xcm::{self, *}, - }; -} - -/// Response data to a query. -#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)] -pub enum Response { - /// Some assets. - Assets(MultiAssets), - /// An XCM version. - Version(super::Version), -} - -/// Cross-Consensus Message: A message from one consensus system to another. -/// -/// Consensus systems that may send and receive messages include blockchains and smart contracts. -/// -/// All messages are delivered from a known *origin*, expressed as a `MultiLocation`. -/// -/// This is the inner XCM format and is version-sensitive. Messages are typically passed using the outer -/// XCM format, known as `VersionedXcm`. -#[derive(Derivative, Encode, Decode, TypeInfo)] -#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] -#[codec(encode_bound())] -#[codec(decode_bound())] -#[scale_info(bounds(), skip_type_params(RuntimeCall))] -pub enum Xcm { - /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place them into `holding`. Execute the - /// orders (`effects`). - /// - /// - `assets`: The asset(s) to be withdrawn into holding. - /// - `effects`: The order(s) to execute on the holding register. - /// - /// Kind: *Instruction*. - /// - /// Errors: - #[codec(index = 0)] - WithdrawAsset { assets: MultiAssets, effects: Vec> }, - - /// Asset(s) (`assets`) have been received into the ownership of this system on the `origin` system. - /// - /// Some orders are given (`effects`) which should be executed once the corresponding derivative assets have - /// been placed into `holding`. - /// - /// - `assets`: The asset(s) that are minted into holding. - /// - `effects`: The order(s) to execute on the holding register. - /// - /// Safety: `origin` must be trusted to have received and be storing `assets` such that they may later be - /// withdrawn should this system send a corresponding message. - /// - /// Kind: *Trusted Indication*. - /// - /// Errors: - #[codec(index = 1)] - ReserveAssetDeposited { assets: MultiAssets, effects: Vec> }, - - /// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets should be - /// created on this system. - /// - /// Some orders are given (`effects`) which should be executed once the corresponding derivative assets have - /// been placed into the Holding Register. - /// - /// - `assets`: The asset(s) that are minted into the Holding Register. - /// - `effects`: The order(s) to execute on the Holding Register. - /// - /// Safety: `origin` must be trusted to have irrevocably destroyed the corresponding `assets` prior as a consequence - /// of sending this message. - /// - /// Kind: *Trusted Indication*. - /// - /// Errors: - #[codec(index = 2)] - ReceiveTeleportedAsset { assets: MultiAssets, effects: Vec> }, - - /// Indication of the contents of the holding register corresponding to the `QueryHolding` order of `query_id`. - /// - /// - `query_id`: The identifier of the query that resulted in this message being sent. - /// - `assets`: The message content. - /// - /// Safety: No concerns. - /// - /// Kind: *Information*. - /// - /// Errors: - #[codec(index = 3)] - QueryResponse { - #[codec(compact)] - query_id: u64, - response: Response, - }, - - /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets under the - /// ownership of `beneficiary`. - /// - /// - `assets`: The asset(s) to be withdrawn. - /// - `beneficiary`: The new owner for the assets. - /// - /// Safety: No concerns. - /// - /// Kind: *Instruction*. - /// - /// Errors: - #[codec(index = 4)] - TransferAsset { assets: MultiAssets, beneficiary: MultiLocation }, - - /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets under the - /// ownership of `dest` within this consensus system (i.e. its sovereign account). - /// - /// Send an onward XCM message to `dest` of `ReserveAssetDeposited` with the given `effects`. - /// - /// - `assets`: The asset(s) to be withdrawn. - /// - `dest`: The location whose sovereign account will own the assets and thus the effective beneficiary for the - /// assets and the notification target for the reserve asset deposit message. - /// - `effects`: The orders that should be contained in the `ReserveAssetDeposited` which is sent onwards to - /// `dest`. - /// - /// Safety: No concerns. - /// - /// Kind: *Instruction*. - /// - /// Errors: - #[codec(index = 5)] - TransferReserveAsset { assets: MultiAssets, dest: MultiLocation, effects: Vec> }, - - /// Apply the encoded transaction `call`, whose dispatch-origin should be `origin` as expressed by the kind - /// of origin `origin_type`. - /// - /// - `origin_type`: The means of expressing the message origin as a dispatch origin. - /// - `max_weight`: The weight of `call`; this should be at least the chain's calculated weight and will - /// be used in the weight determination arithmetic. - /// - `call`: The encoded transaction to be applied. - /// - /// Safety: No concerns. - /// - /// Kind: *Instruction*. - /// - /// Errors: - #[codec(index = 6)] - Transact { - origin_type: OriginKind, - require_weight_at_most: u64, - call: DoubleEncoded, - }, - - /// A message to notify about a new incoming HRMP channel. This message is meant to be sent by the - /// relay-chain to a para. - /// - /// - `sender`: The sender in the to-be opened channel. Also, the initiator of the channel opening. - /// - `max_message_size`: The maximum size of a message proposed by the sender. - /// - `max_capacity`: The maximum number of messages that can be queued in the channel. - /// - /// Safety: The message should originate directly from the relay-chain. - /// - /// Kind: *System Notification* - #[codec(index = 7)] - HrmpNewChannelOpenRequest { - #[codec(compact)] - sender: u32, - #[codec(compact)] - max_message_size: u32, - #[codec(compact)] - max_capacity: u32, - }, - - /// A message to notify about that a previously sent open channel request has been accepted by - /// the recipient. That means that the channel will be opened during the next relay-chain session - /// change. This message is meant to be sent by the relay-chain to a para. - /// - /// Safety: The message should originate directly from the relay-chain. - /// - /// Kind: *System Notification* - /// - /// Errors: - #[codec(index = 8)] - HrmpChannelAccepted { - #[codec(compact)] - recipient: u32, - }, - - /// A message to notify that the other party in an open channel decided to close it. In particular, - /// `initiator` is going to close the channel opened from `sender` to the `recipient`. The close - /// will be enacted at the next relay-chain session change. This message is meant to be sent by - /// the relay-chain to a para. - /// - /// Safety: The message should originate directly from the relay-chain. - /// - /// Kind: *System Notification* - /// - /// Errors: - #[codec(index = 9)] - HrmpChannelClosing { - #[codec(compact)] - initiator: u32, - #[codec(compact)] - sender: u32, - #[codec(compact)] - recipient: u32, - }, - - /// A message to indicate that the embedded XCM is actually arriving on behalf of some consensus - /// location within the origin. - /// - /// Kind: *Instruction* - /// - /// Errors: - #[codec(index = 10)] - RelayedFrom { who: InteriorMultiLocation, message: alloc::boxed::Box> }, - - /// Ask the destination system to respond with the most recent version of XCM that they - /// support in a `QueryResponse` instruction. Any changes to this should also elicit similar - /// responses when they happen. - /// - /// Kind: *Instruction* - #[codec(index = 11)] - SubscribeVersion { - #[codec(compact)] - query_id: u64, - #[codec(compact)] - max_response_weight: u64, - }, - - /// Cancel the effect of a previous `SubscribeVersion` instruction. - /// - /// Kind: *Instruction* - #[codec(index = 12)] - UnsubscribeVersion, -} - -impl Xcm { - pub fn into(self) -> Xcm { - Xcm::from(self) - } - pub fn from(xcm: Xcm) -> Self { - use Xcm::*; - match xcm { - WithdrawAsset { assets, effects } => - WithdrawAsset { assets, effects: effects.into_iter().map(Order::into).collect() }, - ReserveAssetDeposited { assets, effects } => ReserveAssetDeposited { - assets, - effects: effects.into_iter().map(Order::into).collect(), - }, - ReceiveTeleportedAsset { assets, effects } => ReceiveTeleportedAsset { - assets, - effects: effects.into_iter().map(Order::into).collect(), - }, - QueryResponse { query_id, response } => QueryResponse { query_id, response }, - TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary }, - TransferReserveAsset { assets, dest, effects } => - TransferReserveAsset { assets, dest, effects }, - HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => - HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, - HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, - HrmpChannelClosing { initiator, sender, recipient } => - HrmpChannelClosing { initiator, sender, recipient }, - Transact { origin_type, require_weight_at_most, call } => - Transact { origin_type, require_weight_at_most, call: call.into() }, - RelayedFrom { who, message } => - RelayedFrom { who, message: alloc::boxed::Box::new((*message).into()) }, - SubscribeVersion { query_id, max_response_weight } => - SubscribeVersion { query_id, max_response_weight }, - UnsubscribeVersion => UnsubscribeVersion, - } - } -} - -pub mod opaque { - /// The basic concrete type of `generic::Xcm`, which doesn't make any assumptions about the format of a - /// call other than it is pre-encoded. - pub type Xcm = super::Xcm<()>; - - pub use super::order::opaque::*; -} - -// Convert from a v0 response to a v1 response -impl TryFrom for Response { - type Error = (); - fn try_from(old_response: OldResponse) -> result::Result { - match old_response { - OldResponse::Assets(assets) => Ok(Self::Assets(assets.try_into()?)), - } - } -} - -impl TryFrom> for Xcm { - type Error = (); - fn try_from(old: OldXcm) -> result::Result, ()> { - use Xcm::*; - Ok(match old { - OldXcm::WithdrawAsset { assets, effects } => WithdrawAsset { - assets: assets.try_into()?, - effects: effects - .into_iter() - .map(Order::try_from) - .collect::>()?, - }, - OldXcm::ReserveAssetDeposit { assets, effects } => ReserveAssetDeposited { - assets: assets.try_into()?, - effects: effects - .into_iter() - .map(Order::try_from) - .collect::>()?, - }, - OldXcm::TeleportAsset { assets, effects } => ReceiveTeleportedAsset { - assets: assets.try_into()?, - effects: effects - .into_iter() - .map(Order::try_from) - .collect::>()?, - }, - OldXcm::QueryResponse { query_id, response } => - QueryResponse { query_id, response: response.try_into()? }, - OldXcm::TransferAsset { assets, dest } => - TransferAsset { assets: assets.try_into()?, beneficiary: dest.try_into()? }, - OldXcm::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { - assets: assets.try_into()?, - dest: dest.try_into()?, - effects: effects - .into_iter() - .map(Order::try_from) - .collect::>()?, - }, - OldXcm::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => - HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, - OldXcm::HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, - OldXcm::HrmpChannelClosing { initiator, sender, recipient } => - HrmpChannelClosing { initiator, sender, recipient }, - OldXcm::Transact { origin_type, require_weight_at_most, call } => - Transact { origin_type, require_weight_at_most, call: call.into() }, - OldXcm::RelayedFrom { who, message } => RelayedFrom { - who: MultiLocation::try_from(who)?.try_into()?, - message: alloc::boxed::Box::new((*message).try_into()?), - }, - }) - } -} - -impl TryFrom> for Xcm { - type Error = (); - fn try_from(old: NewXcm) -> result::Result, ()> { - use Xcm::*; - let mut iter = old.0.into_iter(); - let instruction = iter.next().ok_or(())?; - Ok(match instruction { - Instruction::WithdrawAsset(assets) => { - let effects = iter.map(Order::try_from).collect::>()?; - WithdrawAsset { assets, effects } - }, - Instruction::ReserveAssetDeposited(assets) => { - if !matches!(iter.next(), Some(Instruction::ClearOrigin)) { - return Err(()) - } - let effects = iter.map(Order::try_from).collect::>()?; - ReserveAssetDeposited { assets, effects } - }, - Instruction::ReceiveTeleportedAsset(assets) => { - if !matches!(iter.next(), Some(Instruction::ClearOrigin)) { - return Err(()) - } - let effects = iter.map(Order::try_from).collect::>()?; - ReceiveTeleportedAsset { assets, effects } - }, - Instruction::QueryResponse { query_id, response, max_weight } => { - // Cannot handle special response weights. - if max_weight > 0 { - return Err(()) - } - QueryResponse { query_id, response: response.try_into()? } - }, - Instruction::TransferAsset { assets, beneficiary } => - TransferAsset { assets, beneficiary }, - Instruction::TransferReserveAsset { assets, dest, xcm } => TransferReserveAsset { - assets, - dest, - effects: xcm - .0 - .into_iter() - .map(Order::try_from) - .collect::>()?, - }, - Instruction::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => - HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, - Instruction::HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, - Instruction::HrmpChannelClosing { initiator, sender, recipient } => - HrmpChannelClosing { initiator, sender, recipient }, - Instruction::Transact { origin_type, require_weight_at_most, call } => - Transact { origin_type, require_weight_at_most, call }, - Instruction::SubscribeVersion { query_id, max_response_weight } => - SubscribeVersion { query_id, max_response_weight }, - Instruction::UnsubscribeVersion => UnsubscribeVersion, - _ => return Err(()), - }) - } -} - -// Convert from a v1 response to a v2 response -impl TryFrom for Response { - type Error = (); - fn try_from(response: NewResponse) -> result::Result { - match response { - NewResponse::Assets(assets) => Ok(Self::Assets(assets)), - NewResponse::Version(version) => Ok(Self::Version(version)), - _ => Err(()), - } - } -} diff --git a/xcm/src/v1/order.rs b/xcm/src/v1/order.rs deleted file mode 100644 index 3076e2d43c36..000000000000 --- a/xcm/src/v1/order.rs +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot 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. - -// Polkadot 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 Polkadot. If not, see . - -//! Version 1 of the Cross-Consensus Message format data structures. - -use super::{MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, Xcm}; -use crate::{v0::Order as OldOrder, v2::Instruction}; -use alloc::{vec, vec::Vec}; -use core::result; -use derivative::Derivative; -use parity_scale_codec::{self, Decode, Encode}; -use scale_info::TypeInfo; - -/// An instruction to be executed on some or all of the assets in holding, used by asset-related XCM messages. -#[derive(Derivative, Encode, Decode, TypeInfo)] -#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] -#[codec(encode_bound())] -#[codec(decode_bound())] -#[scale_info(bounds(), skip_type_params(RuntimeCall))] -pub enum Order { - /// Do nothing. Not generally used. - #[codec(index = 0)] - Noop, - - /// Remove the asset(s) (`assets`) from holding and place equivalent assets under the ownership of `beneficiary` - /// within this consensus system. - /// - /// - `assets`: The asset(s) to remove from holding. - /// - `max_assets`: The maximum number of unique assets/asset instances to remove from holding. Only the first - /// `max_assets` assets/instances of those matched by `assets` will be removed, prioritized under standard asset - /// ordering. Any others will remain in holding. - /// - `beneficiary`: The new owner for the assets. - /// - /// Errors: - #[codec(index = 1)] - DepositAsset { assets: MultiAssetFilter, max_assets: u32, beneficiary: MultiLocation }, - - /// Remove the asset(s) (`assets`) from holding and place equivalent assets under the ownership of `dest` within - /// this consensus system (i.e. its sovereign account). - /// - /// Send an onward XCM message to `dest` of `ReserveAssetDeposited` with the given `effects`. - /// - /// - `assets`: The asset(s) to remove from holding. - /// - `max_assets`: The maximum number of unique assets/asset instances to remove from holding. Only the first - /// `max_assets` assets/instances of those matched by `assets` will be removed, prioritized under standard asset - /// ordering. Any others will remain in holding. - /// - `dest`: The location whose sovereign account will own the assets and thus the effective beneficiary for the - /// assets and the notification target for the reserve asset deposit message. - /// - `effects`: The orders that should be contained in the `ReserveAssetDeposited` which is sent onwards to - /// `dest`. - /// - /// Errors: - #[codec(index = 2)] - DepositReserveAsset { - assets: MultiAssetFilter, - max_assets: u32, - dest: MultiLocation, - effects: Vec>, - }, - - /// Remove the asset(s) (`give`) from holding and replace them with alternative assets. - /// - /// The minimum amount of assets to be received into holding for the order not to fail may be stated. - /// - /// - `give`: The asset(s) to remove from holding. - /// - `receive`: The minimum amount of assets(s) which `give` should be exchanged for. - /// - /// Errors: - #[codec(index = 3)] - ExchangeAsset { give: MultiAssetFilter, receive: MultiAssets }, - - /// Remove the asset(s) (`assets`) from holding and send a `WithdrawAsset` XCM message to a reserve location. - /// - /// - `assets`: The asset(s) to remove from holding. - /// - `reserve`: A valid location that acts as a reserve for all asset(s) in `assets`. The sovereign account - /// of this consensus system *on the reserve location* will have appropriate assets withdrawn and `effects` will - /// be executed on them. There will typically be only one valid location on any given asset/chain combination. - /// - `effects`: The orders to execute on the assets once withdrawn *on the reserve location*. - /// - /// Errors: - #[codec(index = 4)] - InitiateReserveWithdraw { - assets: MultiAssetFilter, - reserve: MultiLocation, - effects: Vec>, - }, - - /// Remove the asset(s) (`assets`) from holding and send a `ReceiveTeleportedAsset` XCM message to a `destination` - /// location. - /// - /// - `assets`: The asset(s) to remove from holding. - /// - `destination`: A valid location that has a bi-lateral teleportation arrangement. - /// - `effects`: The orders to execute on the assets once arrived *on the destination location*. - /// - /// NOTE: The `destination` location *MUST* respect this origin as a valid teleportation origin for all `assets`. - /// If it does not, then the assets may be lost. - /// - /// Errors: - #[codec(index = 5)] - InitiateTeleport { assets: MultiAssetFilter, dest: MultiLocation, effects: Vec> }, - - /// Send a `Balances` XCM message with the `assets` value equal to the holding contents, or a portion thereof. - /// - /// - `query_id`: An identifier that will be replicated into the returned XCM message. - /// - `dest`: A valid destination for the returned XCM message. This may be limited to the current origin. - /// - `assets`: A filter for the assets that should be reported back. The assets reported back will be, asset- - /// wise, *the lesser of this value and the holding register*. No wildcards will be used when reporting assets - /// back. - /// - /// Errors: - #[codec(index = 6)] - QueryHolding { - #[codec(compact)] - query_id: u64, - dest: MultiLocation, - assets: MultiAssetFilter, - }, - - /// Pay for the execution of some XCM `instructions` and `orders` with up to `weight` picoseconds of execution time, - /// paying for this with up to `fees` from the Holding Register. - /// - /// - `fees`: The asset(s) to remove from holding to pay for fees. - /// - `weight`: The amount of weight to purchase; this should be at least the shallow weight of `effects` and `xcm`. - /// - `debt`: The amount of weight-debt already incurred to be paid off; this should be equal to the unpaid weight of - /// any surrounding operations/orders. - /// - `halt_on_error`: If `true`, the execution of the `orders` and `operations` will halt on the first failure. If - /// `false`, then execution will continue regardless. - /// - `instructions`: XCM instructions to be executed outside of the context of the current Holding Register; - /// execution of these instructions happens AFTER the execution of the `orders`. The (shallow) weight for these - /// must be paid for with the `weight` purchased. - /// Errors: - #[codec(index = 7)] - BuyExecution { - fees: MultiAsset, - weight: u64, - debt: u64, - halt_on_error: bool, - instructions: Vec>, - }, -} - -pub mod opaque { - pub type Order = super::Order<()>; -} - -impl Order { - pub fn into(self) -> Order { - Order::from(self) - } - pub fn from(order: Order) -> Self { - use Order::*; - match order { - Noop => Noop, - DepositAsset { assets, max_assets, beneficiary } => - DepositAsset { assets, max_assets, beneficiary }, - DepositReserveAsset { assets, max_assets, dest, effects } => - DepositReserveAsset { assets, max_assets, dest, effects }, - ExchangeAsset { give, receive } => ExchangeAsset { give, receive }, - InitiateReserveWithdraw { assets, reserve, effects } => - InitiateReserveWithdraw { assets, reserve, effects }, - InitiateTeleport { assets, dest, effects } => - InitiateTeleport { assets, dest, effects }, - QueryHolding { query_id, dest, assets } => QueryHolding { query_id, dest, assets }, - BuyExecution { fees, weight, debt, halt_on_error, instructions } => { - let instructions = instructions.into_iter().map(Xcm::from).collect(); - BuyExecution { fees, weight, debt, halt_on_error, instructions } - }, - } - } -} - -impl TryFrom> for Order { - type Error = (); - fn try_from(old: OldOrder) -> result::Result, ()> { - use Order::*; - Ok(match old { - OldOrder::Null => Noop, - OldOrder::DepositAsset { assets, dest } => DepositAsset { - assets: assets.try_into()?, - max_assets: 1, - beneficiary: dest.try_into()?, - }, - OldOrder::DepositReserveAsset { assets, dest, effects } => DepositReserveAsset { - assets: assets.try_into()?, - max_assets: 1, - dest: dest.try_into()?, - effects: effects - .into_iter() - .map(Order::<()>::try_from) - .collect::>()?, - }, - OldOrder::ExchangeAsset { give, receive } => - ExchangeAsset { give: give.try_into()?, receive: receive.try_into()? }, - OldOrder::InitiateReserveWithdraw { assets, reserve, effects } => - InitiateReserveWithdraw { - assets: assets.try_into()?, - reserve: reserve.try_into()?, - effects: effects - .into_iter() - .map(Order::<()>::try_from) - .collect::>()?, - }, - OldOrder::InitiateTeleport { assets, dest, effects } => InitiateTeleport { - assets: assets.try_into()?, - dest: dest.try_into()?, - effects: effects - .into_iter() - .map(Order::<()>::try_from) - .collect::>()?, - }, - OldOrder::QueryHolding { query_id, dest, assets } => - QueryHolding { query_id, dest: dest.try_into()?, assets: assets.try_into()? }, - OldOrder::BuyExecution { fees, weight, debt, halt_on_error, xcm } => { - let instructions = xcm - .into_iter() - .map(Xcm::::try_from) - .collect::>()?; - BuyExecution { fees: fees.try_into()?, weight, debt, halt_on_error, instructions } - }, - }) - } -} - -impl TryFrom> for Order { - type Error = (); - fn try_from(old: Instruction) -> result::Result, ()> { - use Order::*; - Ok(match old { - Instruction::DepositAsset { assets, max_assets, beneficiary } => - DepositAsset { assets, max_assets, beneficiary }, - Instruction::DepositReserveAsset { assets, max_assets, dest, xcm } => - DepositReserveAsset { - assets, - max_assets, - dest, - effects: xcm - .0 - .into_iter() - .map(Order::<()>::try_from) - .collect::>()?, - }, - Instruction::ExchangeAsset { give, receive } => ExchangeAsset { give, receive }, - Instruction::InitiateReserveWithdraw { assets, reserve, xcm } => - InitiateReserveWithdraw { - assets, - reserve, - effects: xcm - .0 - .into_iter() - .map(Order::<()>::try_from) - .collect::>()?, - }, - Instruction::InitiateTeleport { assets, dest, xcm } => InitiateTeleport { - assets, - dest, - effects: xcm - .0 - .into_iter() - .map(Order::<()>::try_from) - .collect::>()?, - }, - Instruction::QueryHolding { query_id, dest, assets, max_response_weight } => { - // Cannot handle special response weights. - if max_response_weight > 0 { - return Err(()) - } - QueryHolding { query_id, dest, assets } - }, - Instruction::BuyExecution { fees, weight_limit } => { - let instructions = vec![]; - let halt_on_error = true; - let weight = 0; - let debt = Option::::from(weight_limit).ok_or(())?; - BuyExecution { fees, weight, debt, halt_on_error, instructions } - }, - _ => return Err(()), - }) - } -} diff --git a/xcm/src/v1/traits.rs b/xcm/src/v1/traits.rs deleted file mode 100644 index e4b3542d41a4..000000000000 --- a/xcm/src/v1/traits.rs +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot 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. - -// Polkadot 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 Polkadot. If not, see . - -//! Cross-Consensus Message format data structures. - -use core::result; -use parity_scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; - -use super::{MultiLocation, Xcm}; - -#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] -pub enum Error { - Undefined, - /// An arithmetic overflow happened. - Overflow, - /// The operation is intentionally unsupported. - Unimplemented, - UnhandledXcmVersion, - /// The implementation does not handle a given XCM. - UnhandledXcmMessage, - /// The implementation does not handle an effect present in an XCM. - UnhandledEffect, - EscalationOfPrivilege, - UntrustedReserveLocation, - UntrustedTeleportLocation, - DestinationBufferOverflow, - /// The message and destination was recognized as being reachable but the operation could not be completed. - /// A human-readable explanation of the specific issue is provided. - SendFailed(#[codec(skip)] &'static str), - /// The message and destination combination was not recognized as being reachable. - CannotReachDestination(MultiLocation, Xcm<()>), - MultiLocationFull, - FailedToDecode, - BadOrigin, - ExceedsMaxMessageSize, - /// An asset transaction (like withdraw or deposit) failed. - /// See implementers of the `TransactAsset` trait for sources. - /// Causes can include type conversion failures between id or balance types. - FailedToTransactAsset(#[codec(skip)] &'static str), - /// Execution of the XCM would potentially result in a greater weight used than the pre-specified - /// weight limit. The amount that is potentially required is the parameter. - WeightLimitReached(Weight), - /// An asset wildcard was passed where it was not expected (e.g. as the asset to withdraw in a - /// `WithdrawAsset` XCM). - Wildcard, - /// The case where an XCM message has specified a weight limit on an interior call and this - /// limit is too low. - /// - /// Used by: - /// - `Transact` - MaxWeightInvalid, - /// The fees specified by the XCM message were not found in the holding register. - /// - /// Used by: - /// - `BuyExecution` - NotHoldingFees, - /// The weight of an XCM message is not computable ahead of execution. This generally means at least part - /// of the message is invalid, which could be due to it containing overly nested structures or an invalid - /// nested data segment (e.g. for the call in `Transact`). - WeightNotComputable, - /// The XCM did not pass the barrier condition for execution. The barrier condition differs on different - /// chains and in different circumstances, but generally it means that the conditions surrounding the message - /// were not such that the chain considers the message worth spending time executing. Since most chains - /// lift the barrier to execution on appropriate payment, presentation of an NFT voucher, or based on the - /// message origin, it means that none of those were the case. - Barrier, - /// Indicates that it is not possible for a location to have an asset be withdrawn or transferred from its - /// ownership. This probably means it doesn't own (enough of) it, but may also indicate that it is under a - /// lock, hold, freeze or is otherwise unavailable. - NotWithdrawable, - /// Indicates that the consensus system cannot deposit an asset under the ownership of a particular location. - LocationCannotHold, - /// The assets given to purchase weight is are insufficient for the weight desired. - TooExpensive, - /// The given asset is not handled. - AssetNotFound, - /// The given message cannot be translated into a format that the destination can be expected to interpret. - DestinationUnsupported, - /// `execute_xcm` has been called too many times recursively. - RecursionLimitReached, -} - -impl From<()> for Error { - fn from(_: ()) -> Self { - Self::Undefined - } -} - -pub type Result = result::Result<(), Error>; - -/// Local weight type; execution time in picoseconds. -pub type Weight = u64; - -/// Outcome of an XCM execution. -#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] -pub enum Outcome { - /// Execution completed successfully; given weight was used. - Complete(Weight), - /// Execution started, but did not complete successfully due to the given error; given weight was used. - Incomplete(Weight, Error), - /// Execution did not start due to the given error. - Error(Error), -} - -impl Outcome { - pub fn ensure_complete(self) -> Result { - match self { - Outcome::Complete(_) => Ok(()), - Outcome::Incomplete(_, e) => Err(e), - Outcome::Error(e) => Err(e), - } - } - pub fn ensure_execution(self) -> result::Result { - match self { - Outcome::Complete(w) => Ok(w), - Outcome::Incomplete(w, _) => Ok(w), - Outcome::Error(e) => Err(e), - } - } - /// How much weight was used by the XCM execution attempt. - pub fn weight_used(&self) -> Weight { - match self { - Outcome::Complete(w) => *w, - Outcome::Incomplete(w, _) => *w, - Outcome::Error(_) => 0, - } - } -} - -/// Type of XCM message executor. -pub trait ExecuteXcm { - /// Execute some XCM `message` from `origin` using no more than `weight_limit` weight. The weight limit is - /// a basic hard-limit and the implementation may place further restrictions or requirements on weight and - /// other aspects. - fn execute_xcm( - origin: impl Into, - message: Xcm, - weight_limit: Weight, - ) -> Outcome { - let origin = origin.into(); - log::debug!( - target: "xcm::execute_xcm", - "origin: {:?}, message: {:?}, weight_limit: {:?}", - origin, - message, - weight_limit, - ); - Self::execute_xcm_in_credit(origin, message, weight_limit, 0) - } - - /// Execute some XCM `message` from `origin` using no more than `weight_limit` weight. - /// - /// Some amount of `weight_credit` may be provided which, depending on the implementation, may allow - /// execution without associated payment. - fn execute_xcm_in_credit( - origin: impl Into, - message: Xcm, - weight_limit: Weight, - weight_credit: Weight, - ) -> Outcome; -} - -impl ExecuteXcm for () { - fn execute_xcm_in_credit( - _origin: impl Into, - _message: Xcm, - _weight_limit: Weight, - _weight_credit: Weight, - ) -> Outcome { - Outcome::Error(Error::Unimplemented) - } -} - -/// Utility for sending an XCM message. -/// -/// These can be amalgamated in tuples to form sophisticated routing systems. In tuple format, each router might return -/// `CannotReachDestination` to pass the execution to the next sender item. Note that each `CannotReachDestination` -/// might alter the destination and the XCM message for to the next router. -/// -/// -/// # Example -/// ```rust -/// # use xcm::v1::{MultiLocation, Xcm, Junction, Junctions, Error, OriginKind, SendXcm, Result, Parent}; -/// # use parity_scale_codec::Encode; -/// -/// /// A sender that only passes the message through and does nothing. -/// struct Sender1; -/// impl SendXcm for Sender1 { -/// fn send_xcm(destination: impl Into, message: Xcm<()>) -> Result { -/// return Err(Error::CannotReachDestination(destination.into(), message)) -/// } -/// } -/// -/// /// A sender that accepts a message that has an X2 junction, otherwise stops the routing. -/// struct Sender2; -/// impl SendXcm for Sender2 { -/// fn send_xcm(destination: impl Into, message: Xcm<()>) -> Result { -/// let destination = destination.into(); -/// if matches!(destination.interior(), Junctions::X2(j1, j2)) -/// && destination.parent_count() == 0 -/// { -/// Ok(()) -/// } else { -/// Err(Error::Undefined) -/// } -/// } -/// } -/// -/// /// A sender that accepts a message from an X1 parent junction, passing through otherwise. -/// struct Sender3; -/// impl SendXcm for Sender3 { -/// fn send_xcm(destination: impl Into, message: Xcm<()>) -> Result { -/// let destination = destination.into(); -/// if matches!(destination.interior(), Junctions::Here) -/// && destination.parent_count() == 1 -/// { -/// Ok(()) -/// } else { -/// Err(Error::CannotReachDestination(destination, message)) -/// } -/// } -/// } -/// -/// // A call to send via XCM. We don't really care about this. -/// # fn main() { -/// let call: Vec = ().encode(); -/// let message = Xcm::Transact { origin_type: OriginKind::Superuser, require_weight_at_most: 0, call: call.into() }; -/// -/// assert!( -/// // Sender2 will block this. -/// <(Sender1, Sender2, Sender3) as SendXcm>::send_xcm(Parent, message.clone()) -/// .is_err() -/// ); -/// -/// assert!( -/// // Sender3 will catch this. -/// <(Sender1, Sender3) as SendXcm>::send_xcm(Parent, message.clone()) -/// .is_ok() -/// ); -/// # } -/// ``` -pub trait SendXcm { - /// Send an XCM `message` to a given `destination`. - /// - /// If it is not a destination which can be reached with this type but possibly could by others, then it *MUST* - /// return `CannotReachDestination`. Any other error will cause the tuple implementation to exit early without - /// trying other type fields. - fn send_xcm(destination: impl Into, message: Xcm<()>) -> Result; -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl SendXcm for Tuple { - fn send_xcm(destination: impl Into, message: Xcm<()>) -> Result { - for_tuples!( #( - // we shadow `destination` and `message` in each expansion for the next one. - let (destination, message) = match Tuple::send_xcm(destination, message) { - Err(Error::CannotReachDestination(d, m)) => (d, m), - o @ _ => return o, - }; - )* ); - Err(Error::CannotReachDestination(destination.into(), message)) - } -} diff --git a/xcm/src/v1/junction.rs b/xcm/src/v2/junction.rs similarity index 78% rename from xcm/src/v1/junction.rs rename to xcm/src/v2/junction.rs index 4ab485beca9f..45c48aa86a8e 100644 --- a/xcm/src/v1/junction.rs +++ b/xcm/src/v2/junction.rs @@ -17,15 +17,16 @@ //! Support data structures for `MultiLocation`, primarily the `Junction` datatype. use super::{BodyId, BodyPart, Junctions, MultiLocation, NetworkId}; -use crate::v0::Junction as Junction0; +use crate::v3::Junction as NewJunction; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_runtime::{traits::ConstU32, WeakBoundedVec}; +use sp_core::{bounded::WeakBoundedVec, ConstU32}; /// A single item in a path to describe the relative location of a consensus system. /// /// Each item assumes a pre-existing location as its context and is defined in terms of it. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum Junction { /// An indexed parachain belonging to and operated by the context. /// @@ -77,23 +78,30 @@ pub enum Junction { Plurality { id: BodyId, part: BodyPart }, } -impl TryFrom for Junction { +impl TryFrom for Junction { type Error = (); - fn try_from(value: Junction0) -> Result { - match value { - Junction0::Parent => Err(()), - Junction0::Parachain(id) => Ok(Self::Parachain(id)), - Junction0::AccountId32 { network, id } => Ok(Self::AccountId32 { network, id }), - Junction0::AccountIndex64 { network, index } => - Ok(Self::AccountIndex64 { network, index }), - Junction0::AccountKey20 { network, key } => Ok(Self::AccountKey20 { network, key }), - Junction0::PalletInstance(index) => Ok(Self::PalletInstance(index)), - Junction0::GeneralIndex(id) => Ok(Self::GeneralIndex(id)), - Junction0::GeneralKey(key) => Ok(Self::GeneralKey(key)), - Junction0::OnlyChild => Ok(Self::OnlyChild), - Junction0::Plurality { id, part } => Ok(Self::Plurality { id: id.into(), part }), - } + fn try_from(value: NewJunction) -> Result { + use NewJunction::*; + Ok(match value { + Parachain(id) => Self::Parachain(id), + AccountId32 { network, id } => Self::AccountId32 { network: network.try_into()?, id }, + AccountIndex64 { network, index } => + Self::AccountIndex64 { network: network.try_into()?, index }, + AccountKey20 { network, key } => + Self::AccountKey20 { network: network.try_into()?, key }, + PalletInstance(index) => Self::PalletInstance(index), + GeneralIndex(id) => Self::GeneralIndex(id), + GeneralKey(key) => Self::GeneralKey( + key[..] + .to_vec() + .try_into() + .expect("array is of size 32 and so will never be out of bounds; qed"), + ), + OnlyChild => Self::OnlyChild, + Plurality { id, part } => Self::Plurality { id: id.into(), part: part.into() }, + _ => return Err(()), + }) } } diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index 50f15689fc2c..aeed60523681 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -50,23 +50,198 @@ //! `DepositAsset` instructions. Failing that, dispatch calls to `teleport_assets` and //! `reserve_transfer_assets` will fail with `UnweighableMessage`. -use super::v1::{Order as OldOrder, Response as OldResponse, Xcm as OldXcm}; -use crate::{DoubleEncoded, GetWeight}; +use super::{ + v3::{ + BodyId as NewBodyId, BodyPart as NewBodyPart, Instruction as NewInstruction, + NetworkId as NewNetworkId, Response as NewResponse, WeightLimit as NewWeightLimit, + Xcm as NewXcm, + }, + DoubleEncoded, GetWeight, +}; use alloc::{vec, vec::Vec}; use core::{fmt::Debug, result}; use derivative::Derivative; -use parity_scale_codec::{self, Decode, Encode}; +use parity_scale_codec::{self, Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; +use sp_core::{bounded::WeakBoundedVec, ConstU32}; +mod junction; +mod multiasset; +mod multilocation; mod traits; -pub use traits::{Error, ExecuteXcm, Outcome, Result, SendError, SendResult, SendXcm}; -// These parts of XCM v1 have been unchanged in XCM v2, and are re-imported here. -pub use super::v1::{ - Ancestor, AncestorThen, AssetId, AssetInstance, BodyId, BodyPart, Fungibility, - InteriorMultiLocation, Junction, Junctions, MultiAsset, MultiAssetFilter, MultiAssets, - MultiLocation, NetworkId, OriginKind, Parent, ParentThen, WildFungibility, WildMultiAsset, +pub use junction::Junction; +pub use multiasset::{ + AssetId, AssetInstance, Fungibility, MultiAsset, MultiAssetFilter, MultiAssets, + WildFungibility, WildMultiAsset, +}; +pub use multilocation::{ + Ancestor, AncestorThen, InteriorMultiLocation, Junctions, MultiLocation, Parent, ParentThen, }; +pub use traits::{Error, ExecuteXcm, Outcome, Result, SendError, SendResult, SendXcm}; + +/// Basically just the XCM (more general) version of `ParachainDispatchOrigin`. +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)] +pub enum OriginKind { + /// Origin should just be the native dispatch origin representation for the sender in the + /// local runtime framework. For Cumulus/Frame chains this is the `Parachain` or `Relay` origin + /// if coming from a chain, though there may be others if the `MultiLocation` XCM origin has a + /// primary/native dispatch origin form. + Native, + + /// Origin should just be the standard account-based origin with the sovereign account of + /// the sender. For Cumulus/Frame chains, this is the `Signed` origin. + SovereignAccount, + + /// Origin should be the super-user. For Cumulus/Frame chains, this is the `Root` origin. + /// This will not usually be an available option. + Superuser, + + /// Origin should be interpreted as an XCM native origin and the `MultiLocation` should be + /// encoded directly in the dispatch origin unchanged. For Cumulus/Frame chains, this will be + /// the `pallet_xcm::Origin::Xcm` type. + Xcm, +} + +/// A global identifier of an account-bearing consensus system. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum NetworkId { + /// Unidentified/any. + Any, + /// Some named network. + Named(WeakBoundedVec>), + /// The Polkadot Relay chain + Polkadot, + /// Kusama. + Kusama, +} + +impl TryInto for Option { + type Error = (); + fn try_into(self) -> result::Result { + use NewNetworkId::*; + Ok(match self { + None => NetworkId::Any, + Some(Polkadot) => NetworkId::Polkadot, + Some(Kusama) => NetworkId::Kusama, + _ => return Err(()), + }) + } +} + +/// An identifier of a pluralistic body. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum BodyId { + /// The only body in its context. + Unit, + /// A named body. + Named(WeakBoundedVec>), + /// An indexed body. + Index(#[codec(compact)] u32), + /// The unambiguous executive body (for Polkadot, this would be the Polkadot council). + Executive, + /// The unambiguous technical body (for Polkadot, this would be the Technical Committee). + Technical, + /// The unambiguous legislative body (for Polkadot, this could be considered the opinion of a majority of + /// lock-voters). + Legislative, + /// The unambiguous judicial body (this doesn't exist on Polkadot, but if it were to get a "grand oracle", it + /// may be considered as that). + Judicial, + /// The unambiguous defense body (for Polkadot, an opinion on the topic given via a public referendum + /// on the `staking_admin` track). + Defense, + /// The unambiguous administration body (for Polkadot, an opinion on the topic given via a public referendum + /// on the `general_admin` track). + Administration, + /// The unambiguous treasury body (for Polkadot, an opinion on the topic given via a public referendum + /// on the `treasurer` track). + Treasury, +} + +impl From for BodyId { + fn from(n: NewBodyId) -> Self { + use NewBodyId::*; + match n { + Unit => Self::Unit, + Moniker(n) => Self::Named( + n[..] + .to_vec() + .try_into() + .expect("array size is 4 and so will never be out of bounds; qed"), + ), + Index(n) => Self::Index(n), + Executive => Self::Executive, + Technical => Self::Technical, + Legislative => Self::Legislative, + Judicial => Self::Judicial, + Defense => Self::Defense, + Administration => Self::Administration, + Treasury => Self::Treasury, + } + } +} + +/// A part of a pluralistic body. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum BodyPart { + /// The body's declaration, under whatever means it decides. + Voice, + /// A given number of members of the body. + Members { + #[codec(compact)] + count: u32, + }, + /// A given number of members of the body, out of some larger caucus. + Fraction { + #[codec(compact)] + nom: u32, + #[codec(compact)] + denom: u32, + }, + /// No less than the given proportion of members of the body. + AtLeastProportion { + #[codec(compact)] + nom: u32, + #[codec(compact)] + denom: u32, + }, + /// More than than the given proportion of members of the body. + MoreThanProportion { + #[codec(compact)] + nom: u32, + #[codec(compact)] + denom: u32, + }, +} + +impl BodyPart { + /// Returns `true` if the part represents a strict majority (> 50%) of the body in question. + pub fn is_majority(&self) -> bool { + match self { + BodyPart::Fraction { nom, denom } if *nom * 2 > *denom => true, + BodyPart::AtLeastProportion { nom, denom } if *nom * 2 > *denom => true, + BodyPart::MoreThanProportion { nom, denom } if *nom * 2 >= *denom => true, + _ => false, + } + } +} + +impl From for BodyPart { + fn from(n: NewBodyPart) -> Self { + use NewBodyPart::*; + match n { + Voice => Self::Voice, + Members { count } => Self::Members { count }, + Fraction { nom, denom } => Self::Fraction { nom, denom }, + AtLeastProportion { nom, denom } => Self::AtLeastProportion { nom, denom }, + MoreThanProportion { nom, denom } => Self::MoreThanProportion { nom, denom }, + } + } +} /// This module's XCM version. pub const VERSION: super::Version = 2; @@ -218,6 +393,17 @@ impl From for Option { } } +impl TryFrom for WeightLimit { + type Error = (); + fn try_from(x: NewWeightLimit) -> result::Result { + use NewWeightLimit::*; + match x { + Limited(w) => Ok(Self::Limited(w.ref_time())), + Unlimited => Ok(Self::Unlimited), + } + } +} + /// Local weight type; execution time in picoseconds. pub type Weight = u64; @@ -422,6 +608,12 @@ pub enum Instruction { /// A `QueryResponse` message of type `ExecutionOutcome` is sent to `dest` with the given /// `query_id` and the outcome of the XCM. /// + /// - `query_id`: An identifier that will be replicated into the returned XCM message. + /// - `dest`: A valid destination for the returned XCM message. + /// - `max_response_weight`: The maximum amount of weight that the `QueryResponse` item which + /// is sent as a reply may take to execute. NOTE: If this is unexpectedly large then the + /// response may not execute at all. + /// /// Kind: *Instruction* /// /// Errors: @@ -633,7 +825,14 @@ pub enum Instruction { /// support in a `QueryResponse` instruction. Any changes to this should also elicit similar /// responses when they happen. /// + /// - `query_id`: An identifier that will be replicated into the returned XCM message. + /// - `max_response_weight`: The maximum amount of weight that the `QueryResponse` item which + /// is sent as a reply may take to execute. NOTE: If this is unexpectedly large then the + /// response may not execute at all. + /// /// Kind: *Instruction* + /// + /// Errors: *Fallible* SubscribeVersion { #[codec(compact)] query_id: QueryId, @@ -644,6 +843,8 @@ pub enum Instruction { /// Cancel the effect of a previous `SubscribeVersion` instruction. /// /// Kind: *Instruction* + /// + /// Errors: *Fallible* UnsubscribeVersion, } @@ -708,48 +909,81 @@ impl Instruction { // TODO: Automate Generation impl> GetWeight for Instruction { - fn weight(&self) -> Weight { + fn weight(&self) -> sp_weights::Weight { use Instruction::*; match self { - WithdrawAsset(assets) => W::withdraw_asset(assets), - ReserveAssetDeposited(assets) => W::reserve_asset_deposited(assets), - ReceiveTeleportedAsset(assets) => W::receive_teleported_asset(assets), + WithdrawAsset(assets) => sp_weights::Weight::from_ref_time(W::withdraw_asset(assets)), + ReserveAssetDeposited(assets) => + sp_weights::Weight::from_ref_time(W::reserve_asset_deposited(assets)), + ReceiveTeleportedAsset(assets) => + sp_weights::Weight::from_ref_time(W::receive_teleported_asset(assets)), QueryResponse { query_id, response, max_weight } => - W::query_response(query_id, response, max_weight), - TransferAsset { assets, beneficiary } => W::transfer_asset(assets, beneficiary), + sp_weights::Weight::from_ref_time(W::query_response(query_id, response, max_weight)), + TransferAsset { assets, beneficiary } => + sp_weights::Weight::from_ref_time(W::transfer_asset(assets, beneficiary)), TransferReserveAsset { assets, dest, xcm } => - W::transfer_reserve_asset(&assets, dest, xcm), + sp_weights::Weight::from_ref_time(W::transfer_reserve_asset(&assets, dest, xcm)), Transact { origin_type, require_weight_at_most, call } => - W::transact(origin_type, require_weight_at_most, call), + sp_weights::Weight::from_ref_time(W::transact( + origin_type, + require_weight_at_most, + call, + )), HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => - W::hrmp_new_channel_open_request(sender, max_message_size, max_capacity), - HrmpChannelAccepted { recipient } => W::hrmp_channel_accepted(recipient), + sp_weights::Weight::from_ref_time(W::hrmp_new_channel_open_request( + sender, + max_message_size, + max_capacity, + )), + HrmpChannelAccepted { recipient } => + sp_weights::Weight::from_ref_time(W::hrmp_channel_accepted(recipient)), HrmpChannelClosing { initiator, sender, recipient } => - W::hrmp_channel_closing(initiator, sender, recipient), - ClearOrigin => W::clear_origin(), - DescendOrigin(who) => W::descend_origin(who), + sp_weights::Weight::from_ref_time(W::hrmp_channel_closing( + initiator, sender, recipient, + )), + ClearOrigin => sp_weights::Weight::from_ref_time(W::clear_origin()), + DescendOrigin(who) => sp_weights::Weight::from_ref_time(W::descend_origin(who)), ReportError { query_id, dest, max_response_weight } => - W::report_error(query_id, dest, max_response_weight), + sp_weights::Weight::from_ref_time(W::report_error( + query_id, + dest, + max_response_weight, + )), DepositAsset { assets, max_assets, beneficiary } => - W::deposit_asset(assets, max_assets, beneficiary), + sp_weights::Weight::from_ref_time(W::deposit_asset(assets, max_assets, beneficiary)), DepositReserveAsset { assets, max_assets, dest, xcm } => - W::deposit_reserve_asset(assets, max_assets, dest, xcm), - ExchangeAsset { give, receive } => W::exchange_asset(give, receive), - InitiateReserveWithdraw { assets, reserve, xcm } => + sp_weights::Weight::from_ref_time(W::deposit_reserve_asset( + assets, max_assets, dest, xcm, + )), + ExchangeAsset { give, receive } => + sp_weights::Weight::from_ref_time(W::exchange_asset(give, receive)), + InitiateReserveWithdraw { assets, reserve, xcm } => sp_weights::Weight::from_ref_time( W::initiate_reserve_withdraw(assets, reserve, xcm), - InitiateTeleport { assets, dest, xcm } => W::initiate_teleport(assets, dest, xcm), + ), + InitiateTeleport { assets, dest, xcm } => + sp_weights::Weight::from_ref_time(W::initiate_teleport(assets, dest, xcm)), QueryHolding { query_id, dest, assets, max_response_weight } => - W::query_holding(query_id, dest, assets, max_response_weight), - BuyExecution { fees, weight_limit } => W::buy_execution(fees, weight_limit), - RefundSurplus => W::refund_surplus(), - SetErrorHandler(xcm) => W::set_error_handler(xcm), - SetAppendix(xcm) => W::set_appendix(xcm), - ClearError => W::clear_error(), - ClaimAsset { assets, ticket } => W::claim_asset(assets, ticket), - Trap(code) => W::trap(code), + sp_weights::Weight::from_ref_time(W::query_holding( + query_id, + dest, + assets, + max_response_weight, + )), + BuyExecution { fees, weight_limit } => + sp_weights::Weight::from_ref_time(W::buy_execution(fees, weight_limit)), + RefundSurplus => sp_weights::Weight::from_ref_time(W::refund_surplus()), + SetErrorHandler(xcm) => sp_weights::Weight::from_ref_time(W::set_error_handler(xcm)), + SetAppendix(xcm) => sp_weights::Weight::from_ref_time(W::set_appendix(xcm)), + ClearError => sp_weights::Weight::from_ref_time(W::clear_error()), + ClaimAsset { assets, ticket } => + sp_weights::Weight::from_ref_time(W::claim_asset(assets, ticket)), + Trap(code) => sp_weights::Weight::from_ref_time(W::trap(code)), SubscribeVersion { query_id, max_response_weight } => - W::subscribe_version(query_id, max_response_weight), - UnsubscribeVersion => W::unsubscribe_version(), + sp_weights::Weight::from_ref_time(W::subscribe_version( + query_id, + max_response_weight, + )), + UnsubscribeVersion => sp_weights::Weight::from_ref_time(W::unsubscribe_version()), } } } @@ -764,180 +998,130 @@ pub mod opaque { pub type Instruction = super::Instruction<()>; } -// Convert from a v1 response to a v2 response -impl TryFrom for Response { +// Convert from a v3 response to a v2 response +impl TryFrom for Response { type Error = (); - fn try_from(old_response: OldResponse) -> result::Result { - match old_response { - OldResponse::Assets(assets) => Ok(Self::Assets(assets)), - OldResponse::Version(version) => Ok(Self::Version(version)), - } + fn try_from(response: NewResponse) -> result::Result { + Ok(match response { + NewResponse::Assets(assets) => Self::Assets(assets.try_into()?), + NewResponse::Version(version) => Self::Version(version), + NewResponse::ExecutionResult(error) => Self::ExecutionResult(match error { + Some((i, e)) => Some((i, e.try_into()?)), + None => None, + }), + NewResponse::Null => Self::Null, + _ => return Err(()), + }) } } -impl TryFrom> for Xcm { +// Convert from a v3 XCM to a v2 XCM. +impl TryFrom> for Xcm { type Error = (); - fn try_from(old: OldXcm) -> result::Result, ()> { - use Instruction::*; - Ok(Xcm(match old { - OldXcm::WithdrawAsset { assets, effects } => Some(Ok(WithdrawAsset(assets))) - .into_iter() - .chain(effects.into_iter().map(Instruction::try_from)) - .collect::, _>>()?, - OldXcm::ReserveAssetDeposited { assets, effects } => - Some(Ok(ReserveAssetDeposited(assets))) - .into_iter() - .chain(Some(Ok(ClearOrigin)).into_iter()) - .chain(effects.into_iter().map(Instruction::try_from)) - .collect::, _>>()?, - OldXcm::ReceiveTeleportedAsset { assets, effects } => - Some(Ok(ReceiveTeleportedAsset(assets))) - .into_iter() - .chain(Some(Ok(ClearOrigin)).into_iter()) - .chain(effects.into_iter().map(Instruction::try_from)) - .collect::, _>>()?, - OldXcm::QueryResponse { query_id, response } => vec![QueryResponse { - query_id, - response: response.try_into()?, - max_weight: 50_000_000, - }], - OldXcm::TransferAsset { assets, beneficiary } => - vec![TransferAsset { assets, beneficiary }], - OldXcm::TransferReserveAsset { assets, dest, effects } => vec![TransferReserveAsset { - assets, - dest, - xcm: Xcm(effects - .into_iter() - .map(Instruction::<()>::try_from) - .collect::>()?), - }], - OldXcm::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => - vec![HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }], - OldXcm::HrmpChannelAccepted { recipient } => vec![HrmpChannelAccepted { recipient }], - OldXcm::HrmpChannelClosing { initiator, sender, recipient } => - vec![HrmpChannelClosing { initiator, sender, recipient }], - OldXcm::Transact { origin_type, require_weight_at_most, call } => - vec![Transact { origin_type, require_weight_at_most, call }], - // We don't handle this one at all due to nested XCM. - OldXcm::RelayedFrom { .. } => return Err(()), - OldXcm::SubscribeVersion { query_id, max_response_weight } => - vec![SubscribeVersion { query_id, max_response_weight }], - OldXcm::UnsubscribeVersion => vec![UnsubscribeVersion], - })) + fn try_from(new_xcm: NewXcm) -> result::Result { + Ok(Xcm(new_xcm.0.into_iter().map(TryInto::try_into).collect::>()?)) } } -impl TryFrom> for Instruction { +// Convert from a v3 instruction to a v2 instruction +impl TryFrom> for Instruction { type Error = (); - fn try_from(old: OldOrder) -> result::Result, ()> { - use Instruction::*; - Ok(match old { - OldOrder::Noop => return Err(()), - OldOrder::DepositAsset { assets, max_assets, beneficiary } => - DepositAsset { assets, max_assets, beneficiary }, - OldOrder::DepositReserveAsset { assets, max_assets, dest, effects } => - DepositReserveAsset { - assets, - max_assets, - dest, - xcm: Xcm(effects - .into_iter() - .map(Instruction::<()>::try_from) - .collect::>()?), - }, - OldOrder::ExchangeAsset { give, receive } => ExchangeAsset { give, receive }, - OldOrder::InitiateReserveWithdraw { assets, reserve, effects } => - InitiateReserveWithdraw { - assets, - reserve, - xcm: Xcm(effects - .into_iter() - .map(Instruction::<()>::try_from) - .collect::>()?), - }, - OldOrder::InitiateTeleport { assets, dest, effects } => InitiateTeleport { - assets, - dest, - xcm: Xcm(effects - .into_iter() - .map(Instruction::<()>::try_from) - .collect::>()?), + fn try_from(instruction: NewInstruction) -> result::Result { + use NewInstruction::*; + Ok(match instruction { + WithdrawAsset(assets) => Self::WithdrawAsset(assets.try_into()?), + ReserveAssetDeposited(assets) => Self::ReserveAssetDeposited(assets.try_into()?), + ReceiveTeleportedAsset(assets) => Self::ReceiveTeleportedAsset(assets.try_into()?), + QueryResponse { query_id, response, max_weight, .. } => Self::QueryResponse { + query_id, + response: response.try_into()?, + max_weight: max_weight.ref_time(), + }, + TransferAsset { assets, beneficiary } => Self::TransferAsset { + assets: assets.try_into()?, + beneficiary: beneficiary.try_into()?, + }, + TransferReserveAsset { assets, dest, xcm } => Self::TransferReserveAsset { + assets: assets.try_into()?, + dest: dest.try_into()?, + xcm: xcm.try_into()?, + }, + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + Self::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, + HrmpChannelAccepted { recipient } => Self::HrmpChannelAccepted { recipient }, + HrmpChannelClosing { initiator, sender, recipient } => + Self::HrmpChannelClosing { initiator, sender, recipient }, + Transact { origin_kind, require_weight_at_most, call } => Self::Transact { + origin_type: origin_kind, + require_weight_at_most: require_weight_at_most.ref_time(), + call: call.into(), + }, + ReportError(response_info) => Self::ReportError { + query_id: response_info.query_id, + dest: response_info.destination.try_into()?, + max_response_weight: response_info.max_weight.ref_time(), + }, + DepositAsset { assets, beneficiary } => { + let max_assets = assets.count().ok_or(())?; + let beneficiary = beneficiary.try_into()?; + let assets = assets.try_into()?; + Self::DepositAsset { assets, max_assets, beneficiary } + }, + DepositReserveAsset { assets, dest, xcm } => { + let max_assets = assets.count().ok_or(())?; + let dest = dest.try_into()?; + let xcm = xcm.try_into()?; + let assets = assets.try_into()?; + Self::DepositReserveAsset { assets, max_assets, dest, xcm } + }, + ExchangeAsset { give, want, .. } => { + let give = give.try_into()?; + let receive = want.try_into()?; + Self::ExchangeAsset { give, receive } + }, + InitiateReserveWithdraw { assets, reserve, xcm } => { + // No `max_assets` here, so if there's a connt, then we cannot translate. + let assets = assets.try_into()?; + let reserve = reserve.try_into()?; + let xcm = xcm.try_into()?; + Self::InitiateReserveWithdraw { assets, reserve, xcm } + }, + InitiateTeleport { assets, dest, xcm } => { + // No `max_assets` here, so if there's a connt, then we cannot translate. + let assets = assets.try_into()?; + let dest = dest.try_into()?; + let xcm = xcm.try_into()?; + Self::InitiateTeleport { assets, dest, xcm } + }, + ReportHolding { response_info, assets } => Self::QueryHolding { + query_id: response_info.query_id, + dest: response_info.destination.try_into()?, + assets: assets.try_into()?, + max_response_weight: response_info.max_weight.ref_time(), }, - OldOrder::QueryHolding { query_id, dest, assets } => - QueryHolding { query_id, dest, assets, max_response_weight: 0 }, - OldOrder::BuyExecution { fees, debt, instructions, .. } => { - // We don't handle nested XCM. - if !instructions.is_empty() { - return Err(()) - } - BuyExecution { fees, weight_limit: WeightLimit::Limited(debt) } + BuyExecution { fees, weight_limit } => { + let fees = fees.try_into()?; + let weight_limit = weight_limit.try_into()?; + Self::BuyExecution { fees, weight_limit } }, + ClearOrigin => Self::ClearOrigin, + DescendOrigin(who) => Self::DescendOrigin(who.try_into()?), + RefundSurplus => Self::RefundSurplus, + SetErrorHandler(xcm) => Self::SetErrorHandler(xcm.try_into()?), + SetAppendix(xcm) => Self::SetAppendix(xcm.try_into()?), + ClearError => Self::ClearError, + ClaimAsset { assets, ticket } => { + let assets = assets.try_into()?; + let ticket = ticket.try_into()?; + Self::ClaimAsset { assets, ticket } + }, + Trap(code) => Self::Trap(code), + SubscribeVersion { query_id, max_response_weight } => Self::SubscribeVersion { + query_id, + max_response_weight: max_response_weight.ref_time(), + }, + UnsubscribeVersion => Self::UnsubscribeVersion, + _ => return Err(()), }) } } - -#[cfg(test)] -mod tests { - use super::{prelude::*, *}; - - #[test] - fn basic_roundtrip_works() { - let xcm = - Xcm::<()>(vec![TransferAsset { assets: (Here, 1).into(), beneficiary: Here.into() }]); - let old_xcm = - OldXcm::<()>::TransferAsset { assets: (Here, 1).into(), beneficiary: Here.into() }; - assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); - let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); - assert_eq!(new_xcm, xcm); - } - - #[test] - fn teleport_roundtrip_works() { - let xcm = Xcm::<()>(vec![ - ReceiveTeleportedAsset((Here, 1).into()), - ClearOrigin, - DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Here.into() }, - ]); - let old_xcm: OldXcm<()> = OldXcm::<()>::ReceiveTeleportedAsset { - assets: (Here, 1).into(), - effects: vec![OldOrder::DepositAsset { - assets: Wild(All), - max_assets: 1, - beneficiary: Here.into(), - }], - }; - assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); - let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); - assert_eq!(new_xcm, xcm); - } - - #[test] - fn reserve_deposit_roundtrip_works() { - let xcm = Xcm::<()>(vec![ - ReserveAssetDeposited((Here, 1).into()), - ClearOrigin, - BuyExecution { fees: (Here, 1).into(), weight_limit: Some(1).into() }, - DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Here.into() }, - ]); - let old_xcm: OldXcm<()> = OldXcm::<()>::ReserveAssetDeposited { - assets: (Here, 1).into(), - effects: vec![ - OldOrder::BuyExecution { - fees: (Here, 1).into(), - debt: 1, - weight: 0, - instructions: vec![], - halt_on_error: true, - }, - OldOrder::DepositAsset { - assets: Wild(All), - max_assets: 1, - beneficiary: Here.into(), - }, - ], - }; - assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); - let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); - assert_eq!(new_xcm, xcm); - } -} diff --git a/xcm/src/v1/multiasset.rs b/xcm/src/v2/multiasset.rs similarity index 81% rename from xcm/src/v1/multiasset.rs rename to xcm/src/v2/multiasset.rs index 10e49af8d2df..6493b7c1afbe 100644 --- a/xcm/src/v1/multiasset.rs +++ b/xcm/src/v2/multiasset.rs @@ -24,13 +24,20 @@ //! account. use super::MultiLocation; +use crate::v3::{ + AssetId as NewAssetId, AssetInstance as NewAssetInstance, Fungibility as NewFungibility, + MultiAsset as NewMultiAsset, MultiAssetFilter as NewMultiAssetFilter, + MultiAssets as NewMultiAssets, WildFungibility as NewWildFungibility, + WildMultiAsset as NewWildMultiAsset, +}; use alloc::{vec, vec::Vec}; -use core::{cmp::Ordering, result}; +use core::cmp::Ordering; use parity_scale_codec::{self as codec, Decode, Encode}; use scale_info::TypeInfo; /// A general identifier for an instance of a non-fungible asset class. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum AssetInstance { /// Undefined - used if the non-fungible asset class has only one instance. Undefined, @@ -91,8 +98,24 @@ impl From> for AssetInstance { } } +impl TryFrom for AssetInstance { + type Error = (); + fn try_from(value: NewAssetInstance) -> Result { + use NewAssetInstance::*; + Ok(match value { + Undefined => Self::Undefined, + Index(n) => Self::Index(n), + Array4(n) => Self::Array4(n), + Array8(n) => Self::Array8(n), + Array16(n) => Self::Array16(n), + Array32(n) => Self::Array32(n), + }) + } +} + /// Classification of an asset being concrete or abstract. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum AssetId { Concrete(MultiLocation), Abstract(Vec), @@ -110,6 +133,20 @@ impl From> for AssetId { } } +impl TryFrom for AssetId { + type Error = (); + fn try_from(old: NewAssetId) -> Result { + use NewAssetId::*; + Ok(match old { + Concrete(l) => Self::Concrete(l.try_into()?), + Abstract(v) => { + let zeroes = v.iter().rev().position(|n| *n != 0).unwrap_or(v.len()); + Self::Abstract(v[0..(32 - zeroes)].to_vec()) + }, + }) + } +} + impl AssetId { /// Prepend a `MultiLocation` to a concrete asset, giving it a new root location. pub fn prepend_with(&mut self, prepend: &MultiLocation) -> Result<(), ()> { @@ -142,6 +179,7 @@ impl AssetId { /// Classification of whether an asset is fungible or not, along with a mandatory amount or instance. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum Fungibility { Fungible(#[codec(compact)] u128), NonFungible(AssetInstance), @@ -168,7 +206,19 @@ impl> From for Fungibility { } } +impl TryFrom for Fungibility { + type Error = (); + fn try_from(value: NewFungibility) -> Result { + use NewFungibility::*; + Ok(match value { + Fungible(n) => Self::Fungible(n), + NonFungible(i) => Self::NonFungible(i.try_into()?), + }) + } +} + #[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct MultiAsset { pub id: AssetId, pub fun: Fungibility, @@ -243,47 +293,16 @@ impl MultiAsset { } } -impl TryFrom for MultiAsset { +impl TryFrom for MultiAsset { type Error = (); - fn try_from(old: super::super::v0::MultiAsset) -> result::Result { - use super::super::v0::MultiAsset as V0; - use AssetId::*; - use Fungibility::*; - let (id, fun) = match old { - V0::ConcreteFungible { id, amount } => (Concrete(id.try_into()?), Fungible(amount)), - V0::ConcreteNonFungible { class, instance } => - (Concrete(class.try_into()?), NonFungible(instance)), - V0::AbstractFungible { id, amount } => (Abstract(id), Fungible(amount)), - V0::AbstractNonFungible { class, instance } => (Abstract(class), NonFungible(instance)), - _ => return Err(()), - }; - Ok(MultiAsset { id, fun }) - } -} - -impl TryFrom for Option { - type Error = (); - fn try_from(old: super::super::v0::MultiAsset) -> result::Result, ()> { - match old { - super::super::v0::MultiAsset::None => return Ok(None), - x => return Ok(Some(x.try_into()?)), - } - } -} - -impl TryFrom> for MultiAsset { - type Error = (); - fn try_from(mut old: Vec) -> result::Result { - if old.len() == 1 { - old.remove(0).try_into() - } else { - Err(()) - } + fn try_from(new: NewMultiAsset) -> Result { + Ok(Self { id: new.id.try_into()?, fun: new.fun.try_into()? }) } } /// A `Vec` of `MultiAsset`s. There may be no duplicate fungible items in here and when decoding, they must be sorted. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct MultiAssets(Vec); impl Decode for MultiAssets { @@ -293,15 +312,15 @@ impl Decode for MultiAssets { } } -impl TryFrom> for MultiAssets { +impl TryFrom for MultiAssets { type Error = (); - fn try_from(old: Vec) -> result::Result { - let v = old + fn try_from(new: NewMultiAssets) -> Result { + let v = new + .into_inner() .into_iter() - .map(Option::::try_from) - .filter_map(|x| x.transpose()) - .collect::, ()>>()?; - Ok(v.into()) + .map(MultiAsset::try_from) + .collect::, ()>>()?; + Ok(MultiAssets(v)) } } @@ -435,7 +454,8 @@ impl MultiAssets { self.0.iter_mut().try_for_each(|i| i.prepend_with(prefix)) } - /// Prepend a `MultiLocation` to any concrete asset items, giving it a new root location. + /// Mutate the location of the asset identifier if concrete, giving it the same location + /// relative to a `target` context. The local context is provided as `ancestry`. pub fn reanchor(&mut self, target: &MultiLocation, ancestry: &MultiLocation) -> Result<(), ()> { self.0.iter_mut().try_for_each(|i| i.reanchor(target, ancestry)) } @@ -445,15 +465,29 @@ impl MultiAssets { self.0.get(index) } } + /// Classification of whether an asset is fungible or not. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum WildFungibility { Fungible, NonFungible, } +impl TryFrom for WildFungibility { + type Error = (); + fn try_from(value: NewWildFungibility) -> Result { + use NewWildFungibility::*; + Ok(match value { + Fungible => Self::Fungible, + NonFungible => Self::NonFungible, + }) + } +} + /// A wildcard representing a set of assets. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum WildMultiAsset { /// All assets in the holding register, up to `usize` individual assets (different instances of non-fungibles could /// be separate assets). @@ -463,35 +497,6 @@ pub enum WildMultiAsset { AllOf { id: AssetId, fun: WildFungibility }, } -impl TryFrom for WildMultiAsset { - type Error = (); - fn try_from(old: super::super::v0::MultiAsset) -> result::Result { - use super::super::v0::MultiAsset as V0; - use AssetId::*; - use WildFungibility::*; - let (id, fun) = match old { - V0::All => return Ok(WildMultiAsset::All), - V0::AllConcreteFungible { id } => (Concrete(id.try_into()?), Fungible), - V0::AllConcreteNonFungible { class } => (Concrete(class.try_into()?), NonFungible), - V0::AllAbstractFungible { id } => (Abstract(id), Fungible), - V0::AllAbstractNonFungible { class } => (Abstract(class), NonFungible), - _ => return Err(()), - }; - Ok(WildMultiAsset::AllOf { id, fun }) - } -} - -impl TryFrom> for WildMultiAsset { - type Error = (); - fn try_from(mut old: Vec) -> result::Result { - if old.len() == 1 { - old.remove(0).try_into() - } else { - Err(()) - } - } -} - impl WildMultiAsset { /// Returns true if `self` is a super-set of the given `inner`. /// @@ -505,7 +510,8 @@ impl WildMultiAsset { } } - /// Prepend a `MultiLocation` to any concrete asset components, giving it a new root location. + /// Mutate the location of the asset identifier if concrete, giving it the same location + /// relative to a `target` context. The local context is provided as `ancestry`. pub fn reanchor(&mut self, target: &MultiLocation, ancestry: &MultiLocation) -> Result<(), ()> { use WildMultiAsset::*; match self { @@ -526,6 +532,7 @@ impl, B: Into> From<(A, B)> for WildMultiAsset /// Note: Vectors of wildcards whose encoding is supported in XCM v0 are unsupported /// in this implementation and will result in a decode error. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum MultiAssetFilter { Definite(MultiAssets), Wild(WildMultiAsset), @@ -567,7 +574,8 @@ impl MultiAssetFilter { } } - /// Prepend a `MultiLocation` to any concrete asset components, giving it a new root location. + /// Mutate the location of the asset identifier if concrete, giving it the same location + /// relative to a `target` context. The local context is provided as `ancestry`. pub fn reanchor(&mut self, target: &MultiLocation, ancestry: &MultiLocation) -> Result<(), ()> { match self { MultiAssetFilter::Definite(ref mut assets) => assets.reanchor(target, ancestry), @@ -576,15 +584,25 @@ impl MultiAssetFilter { } } -impl TryFrom> for MultiAssetFilter { +impl TryFrom for WildMultiAsset { type Error = (); - fn try_from( - mut old: Vec, - ) -> result::Result { - if old.len() == 1 && old[0].is_wildcard() { - Ok(MultiAssetFilter::Wild(old.remove(0).try_into()?)) - } else { - Ok(MultiAssetFilter::Definite(old.try_into()?)) - } + fn try_from(new: NewWildMultiAsset) -> Result { + use NewWildMultiAsset::*; + Ok(match new { + AllOf { id, fun } | AllOfCounted { id, fun, .. } => + Self::AllOf { id: id.try_into()?, fun: fun.try_into()? }, + All | AllCounted(_) => Self::All, + }) + } +} + +impl TryFrom for MultiAssetFilter { + type Error = (); + fn try_from(old: NewMultiAssetFilter) -> Result { + use NewMultiAssetFilter::*; + Ok(match old { + Definite(x) => Self::Definite(x.try_into()?), + Wild(x) => Self::Wild(x.try_into()?), + }) } } diff --git a/xcm/src/v1/multilocation.rs b/xcm/src/v2/multilocation.rs similarity index 95% rename from xcm/src/v1/multilocation.rs rename to xcm/src/v2/multilocation.rs index 83cf0095c3b8..4abdc1daf7a6 100644 --- a/xcm/src/v1/multilocation.rs +++ b/xcm/src/v2/multilocation.rs @@ -17,6 +17,7 @@ //! Cross-Consensus Message format data structures. use super::Junction; +use crate::v3::MultiLocation as NewMultiLocation; use core::{mem, result}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -48,6 +49,7 @@ use scale_info::TypeInfo; /// /// The `MultiLocation` value of `Null` simply refers to the interpreting consensus system. #[derive(Clone, Decode, Encode, Eq, PartialEq, Ord, PartialOrd, Debug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct MultiLocation { /// The number of parent junctions at the beginning of this `MultiLocation`. pub parents: u8, @@ -236,7 +238,7 @@ impl MultiLocation { /// /// # Example /// ```rust - /// # use xcm::v1::{Junctions::*, Junction::*, MultiLocation}; + /// # use xcm::v2::{Junctions::*, Junction::*, MultiLocation}; /// # fn main() { /// let mut m = MultiLocation::new(1, X2(PalletInstance(3), OnlyChild)); /// assert_eq!( @@ -258,7 +260,7 @@ impl MultiLocation { /// /// # Example /// ```rust - /// # use xcm::v1::{Junctions::*, Junction::*, MultiLocation}; + /// # use xcm::v2::{Junctions::*, Junction::*, MultiLocation}; /// let m = MultiLocation::new(1, X3(PalletInstance(3), OnlyChild, OnlyChild)); /// assert!(m.starts_with(&MultiLocation::new(1, X1(PalletInstance(3))))); /// assert!(!m.starts_with(&MultiLocation::new(1, X1(GeneralIndex(99))))); @@ -277,7 +279,7 @@ impl MultiLocation { /// /// # Example /// ```rust - /// # use xcm::v1::{Junctions::*, Junction::*, MultiLocation}; + /// # use xcm::v2::{Junctions::*, Junction::*, MultiLocation}; /// # fn main() { /// let mut m = MultiLocation::new(1, X1(Parachain(21))); /// assert_eq!(m.append_with(X1(PalletInstance(3))), Ok(())); @@ -300,7 +302,7 @@ impl MultiLocation { /// /// # Example /// ```rust - /// # use xcm::v1::{Junctions::*, Junction::*, MultiLocation}; + /// # use xcm::v2::{Junctions::*, Junction::*, MultiLocation}; /// # fn main() { /// let mut m = MultiLocation::new(2, X1(PalletInstance(3))); /// assert_eq!(m.prepend_with(MultiLocation::new(1, X2(Parachain(21), OnlyChild))), Ok(())); @@ -342,6 +344,21 @@ impl MultiLocation { Ok(()) } + /// Consume `self` and return the value representing the same location from the point of view + /// of `target`. The context of `self` is provided as `ancestry`. + /// + /// Returns an `Err` with the unmodified `self` in the case of error. + pub fn reanchored( + mut self, + target: &MultiLocation, + ancestry: &MultiLocation, + ) -> Result { + match self.reanchor(target, ancestry) { + Ok(()) => Ok(self), + Err(()) => Err(self), + } + } + /// Mutate `self` so that it represents the same location from the point of view of `target`. /// The context of `self` is provided as `ancestry`. /// @@ -397,6 +414,13 @@ impl MultiLocation { } } +impl TryFrom for MultiLocation { + type Error = (); + fn try_from(x: NewMultiLocation) -> result::Result { + Ok(MultiLocation { parents: x.parents, interior: x.interior.try_into()? }) + } +} + /// A unit struct which can be converted into a `MultiLocation` of `parents` value 1. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Parent; @@ -408,7 +432,7 @@ impl From for MultiLocation { /// A tuple struct which can be converted into a `MultiLocation` of `parents` value 1 with the inner interior. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct ParentThen(Junctions); +pub struct ParentThen(pub Junctions); impl From for MultiLocation { fn from(ParentThen(interior): ParentThen) -> Self { MultiLocation { parents: 1, interior } @@ -417,7 +441,7 @@ impl From for MultiLocation { /// A unit struct which can be converted into a `MultiLocation` of the inner `parents` value. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct Ancestor(u8); +pub struct Ancestor(pub u8); impl From for MultiLocation { fn from(Ancestor(parents): Ancestor) -> Self { MultiLocation { parents, interior: Junctions::Here } @@ -426,14 +450,14 @@ impl From for MultiLocation { /// A unit struct which can be converted into a `MultiLocation` of the inner `parents` value and the inner interior. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct AncestorThen(u8, Junctions); -impl From for MultiLocation { - fn from(AncestorThen(parents, interior): AncestorThen) -> Self { - MultiLocation { parents, interior } +pub struct AncestorThen(pub u8, pub Interior); +impl> From> for MultiLocation { + fn from(AncestorThen(parents, interior): AncestorThen) -> Self { + MultiLocation { parents, interior: interior.into() } } } -xcm_procedural::impl_conversion_functions_for_multilocation_v1!(); +xcm_procedural::impl_conversion_functions_for_multilocation_v2!(); /// Maximum number of `Junction`s that a `Junctions` can contain. const MAX_JUNCTIONS: usize = 8; @@ -444,6 +468,7 @@ const MAX_JUNCTIONS: usize = 8; /// Parent junctions cannot be constructed with this type. Refer to `MultiLocation` for /// instructions on constructing parent junctions. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum Junctions { /// The interpreting consensus system. Here, @@ -811,7 +836,7 @@ impl Junctions { /// /// # Example /// ```rust - /// # use xcm::v1::{Junctions::*, Junction::*}; + /// # use xcm::v2::{Junctions::*, Junction::*}; /// # fn main() { /// let mut m = X3(Parachain(2), PalletInstance(3), OnlyChild); /// assert_eq!(m.match_and_split(&X2(Parachain(2), PalletInstance(3))), Some(&OnlyChild)); @@ -829,7 +854,7 @@ impl Junctions { /// /// # Example /// ```rust - /// # use xcm::v1::{Junctions::*, Junction::*}; + /// # use xcm::v2::{Junctions::*, Junction::*}; /// let mut j = X3(Parachain(2), PalletInstance(3), OnlyChild); /// assert!(j.starts_with(&X2(Parachain(2), PalletInstance(3)))); /// assert!(j.starts_with(&j)); @@ -859,7 +884,7 @@ impl TryFrom for Junctions { #[cfg(test)] mod tests { use super::{Ancestor, AncestorThen, Junctions::*, MultiLocation, Parent, ParentThen}; - use crate::opaque::v1::{Junction::*, NetworkId::*}; + use crate::opaque::v2::{Junction::*, NetworkId::*}; use parity_scale_codec::{Decode, Encode}; #[test] @@ -1062,7 +1087,6 @@ mod tests { #[test] fn conversion_from_other_types_works() { - use crate::v0; fn takes_multilocation>(_arg: Arg) {} takes_multilocation(Parent); @@ -1079,27 +1103,5 @@ mod tests { takes_multilocation((Parent, Here)); takes_multilocation(ParentThen(X1(Parachain(75)))); takes_multilocation([Parachain(100), PalletInstance(3)]); - - assert_eq!(v0::MultiLocation::Null.try_into(), Ok(MultiLocation::here())); - assert_eq!( - v0::MultiLocation::X1(v0::Junction::Parent).try_into(), - Ok(MultiLocation::parent()) - ); - assert_eq!( - v0::MultiLocation::X2(v0::Junction::Parachain(88), v0::Junction::Parent).try_into(), - Ok(MultiLocation::here()), - ); - assert_eq!( - v0::MultiLocation::X3( - v0::Junction::Parent, - v0::Junction::Parent, - v0::Junction::GeneralKey(b"foo".to_vec().try_into().unwrap()), - ) - .try_into(), - Ok(MultiLocation { - parents: 2, - interior: X1(GeneralKey(b"foo".to_vec().try_into().unwrap())) - }), - ); } } diff --git a/xcm/src/v2/traits.rs b/xcm/src/v2/traits.rs index 94d48034fc36..77900e514c4b 100644 --- a/xcm/src/v2/traits.rs +++ b/xcm/src/v2/traits.rs @@ -16,6 +16,7 @@ //! Cross-Consensus Message format data structures. +use crate::v3::Error as NewError; use core::result; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; @@ -110,10 +111,42 @@ pub enum Error { WeightNotComputable, } +impl TryFrom for Error { + type Error = (); + fn try_from(new_error: NewError) -> result::Result { + use NewError::*; + Ok(match new_error { + Overflow => Self::Overflow, + Unimplemented => Self::Unimplemented, + UntrustedReserveLocation => Self::UntrustedReserveLocation, + UntrustedTeleportLocation => Self::UntrustedTeleportLocation, + LocationFull => Self::MultiLocationFull, + LocationNotInvertible => Self::MultiLocationNotInvertible, + BadOrigin => Self::BadOrigin, + InvalidLocation => Self::InvalidLocation, + AssetNotFound => Self::AssetNotFound, + FailedToTransactAsset(s) => Self::FailedToTransactAsset(s), + NotWithdrawable => Self::NotWithdrawable, + LocationCannotHold => Self::LocationCannotHold, + ExceedsMaxMessageSize => Self::ExceedsMaxMessageSize, + DestinationUnsupported => Self::DestinationUnsupported, + Transport(s) => Self::Transport(s), + Unroutable => Self::Unroutable, + UnknownClaim => Self::UnknownClaim, + FailedToDecode => Self::FailedToDecode, + MaxWeightInvalid => Self::MaxWeightInvalid, + NotHoldingFees => Self::NotHoldingFees, + TooExpensive => Self::TooExpensive, + Trap(i) => Self::Trap(i), + _ => return Err(()), + }) + } +} + impl From for Error { fn from(e: SendError) -> Self { match e { - SendError::CannotReachDestination(..) | SendError::Unroutable => Error::Unroutable, + SendError::NotApplicable(..) | SendError::Unroutable => Error::Unroutable, SendError::Transport(s) => Error::Transport(s), SendError::DestinationUnsupported => Error::DestinationUnsupported, SendError::ExceedsMaxMessageSize => Error::ExceedsMaxMessageSize, @@ -210,7 +243,7 @@ pub enum SendError { /// /// This is not considered fatal: if there are alternative transport routes available, then /// they may be attempted. For this reason, the destination and message are contained. - CannotReachDestination(MultiLocation, Xcm<()>), + NotApplicable(MultiLocation, Xcm<()>), /// Destination is routable, but there is some issue with the transport mechanism. This is /// considered fatal. /// A human-readable explanation of the specific issue is provided. @@ -231,7 +264,7 @@ pub type SendResult = result::Result<(), SendError>; /// Utility for sending an XCM message. /// /// These can be amalgamated in tuples to form sophisticated routing systems. In tuple format, each router might return -/// `CannotReachDestination` to pass the execution to the next sender item. Note that each `CannotReachDestination` +/// `NotApplicable` to pass the execution to the next sender item. Note that each `NotApplicable` /// might alter the destination and the XCM message for to the next router. /// /// @@ -244,7 +277,7 @@ pub type SendResult = result::Result<(), SendError>; /// struct Sender1; /// impl SendXcm for Sender1 { /// fn send_xcm(destination: impl Into, message: Xcm<()>) -> SendResult { -/// return Err(SendError::CannotReachDestination(destination.into(), message)) +/// return Err(SendError::NotApplicable(destination.into(), message)) /// } /// } /// @@ -267,7 +300,7 @@ pub type SendResult = result::Result<(), SendError>; /// let destination = destination.into(); /// match destination { /// MultiLocation { parents: 1, interior: Here } => Ok(()), -/// _ => Err(SendError::CannotReachDestination(destination, message)), +/// _ => Err(SendError::NotApplicable(destination, message)), /// } /// } /// } @@ -298,7 +331,7 @@ pub trait SendXcm { /// Send an XCM `message` to a given `destination`. /// /// If it is not a destination which can be reached with this type but possibly could by others, then it *MUST* - /// return `CannotReachDestination`. Any other error will cause the tuple implementation to exit early without + /// return `NotApplicable`. Any other error will cause the tuple implementation to exit early without /// trying other type fields. fn send_xcm(destination: impl Into, message: Xcm<()>) -> SendResult; } @@ -309,10 +342,10 @@ impl SendXcm for Tuple { for_tuples!( #( // we shadow `destination` and `message` in each expansion for the next one. let (destination, message) = match Tuple::send_xcm(destination, message) { - Err(SendError::CannotReachDestination(d, m)) => (d, m), + Err(SendError::NotApplicable(d, m)) => (d, m), o @ _ => return o, }; )* ); - Err(SendError::CannotReachDestination(destination.into(), message)) + Err(SendError::NotApplicable(destination.into(), message)) } } diff --git a/xcm/src/v3/junction.rs b/xcm/src/v3/junction.rs new file mode 100644 index 000000000000..7c1050624477 --- /dev/null +++ b/xcm/src/v3/junction.rs @@ -0,0 +1,342 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! Support data structures for `MultiLocation`, primarily the `Junction` datatype. + +use super::{Junctions, MultiLocation}; +use crate::{ + v2::{ + BodyId as OldBodyId, BodyPart as OldBodyPart, Junction as OldJunction, + NetworkId as OldNetworkId, + }, + VersionedMultiLocation, +}; +use core::convert::{TryFrom, TryInto}; +use parity_scale_codec::{self, Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +/// A global identifier of a data structure existing within consensus. +/// +/// Maintenance note: Networks with global consensus and which are practically bridgeable within the +/// Polkadot ecosystem are given preference over explicit naming in this enumeration. +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen, +)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum NetworkId { + /// Network specified by the first 32 bytes of its genesis block. + ByGenesis([u8; 32]), + /// Network defined by the first 32-bytes of the hash and number of some block it contains. + ByFork { block_number: u64, block_hash: [u8; 32] }, + /// The Polkadot mainnet Relay-chain. + Polkadot, + /// The Kusama canary-net Relay-chain. + Kusama, + /// The Westend testnet Relay-chain. + Westend, + /// The Rococo testnet Relay-chain. + Rococo, + /// The Wococo testnet Relay-chain. + Wococo, + /// An Ethereum network specified by its chain ID. + Ethereum { + /// The EIP-155 chain ID. + #[codec(compact)] + chain_id: u64, + }, + /// The Bitcoin network, including hard-forks supported by Bitcoin Core development team. + BitcoinCore, + /// The Bitcoin network, including hard-forks supported by Bitcoin Cash developers. + BitcoinCash, +} + +impl From for Option { + fn from(old: OldNetworkId) -> Option { + use OldNetworkId::*; + match old { + Any => None, + Named(_) => None, + Polkadot => Some(NetworkId::Polkadot), + Kusama => Some(NetworkId::Kusama), + } + } +} + +/// An identifier of a pluralistic body. +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen, +)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum BodyId { + /// The only body in its context. + Unit, + /// A named body. + Moniker([u8; 4]), + /// An indexed body. + Index(#[codec(compact)] u32), + /// The unambiguous executive body (for Polkadot, this would be the Polkadot council). + Executive, + /// The unambiguous technical body (for Polkadot, this would be the Technical Committee). + Technical, + /// The unambiguous legislative body (for Polkadot, this could be considered the opinion of a majority of + /// lock-voters). + Legislative, + /// The unambiguous judicial body (this doesn't exist on Polkadot, but if it were to get a "grand oracle", it + /// may be considered as that). + Judicial, + /// The unambiguous defense body (for Polkadot, an opinion on the topic given via a public referendum + /// on the `staking_admin` track). + Defense, + /// The unambiguous administration body (for Polkadot, an opinion on the topic given via a public referendum + /// on the `general_admin` track). + Administration, + /// The unambiguous treasury body (for Polkadot, an opinion on the topic given via a public referendum + /// on the `treasurer` track). + Treasury, +} + +impl TryFrom for BodyId { + type Error = (); + fn try_from(value: OldBodyId) -> Result { + use OldBodyId::*; + Ok(match value { + Unit => Self::Unit, + Named(n) => + if n.len() == 4 { + let mut r = [0u8; 4]; + r.copy_from_slice(&n[..]); + Self::Moniker(r) + } else { + return Err(()) + }, + Index(n) => Self::Index(n), + Executive => Self::Executive, + Technical => Self::Technical, + Legislative => Self::Legislative, + Judicial => Self::Judicial, + Defense => Self::Defense, + Administration => Self::Administration, + Treasury => Self::Treasury, + }) + } +} + +/// A part of a pluralistic body. +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen, +)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum BodyPart { + /// The body's declaration, under whatever means it decides. + Voice, + /// A given number of members of the body. + Members { + #[codec(compact)] + count: u32, + }, + /// A given number of members of the body, out of some larger caucus. + Fraction { + #[codec(compact)] + nom: u32, + #[codec(compact)] + denom: u32, + }, + /// No less than the given proportion of members of the body. + AtLeastProportion { + #[codec(compact)] + nom: u32, + #[codec(compact)] + denom: u32, + }, + /// More than than the given proportion of members of the body. + MoreThanProportion { + #[codec(compact)] + nom: u32, + #[codec(compact)] + denom: u32, + }, +} + +impl BodyPart { + /// Returns `true` if the part represents a strict majority (> 50%) of the body in question. + pub fn is_majority(&self) -> bool { + match self { + BodyPart::Fraction { nom, denom } if *nom * 2 > *denom => true, + BodyPart::AtLeastProportion { nom, denom } if *nom * 2 > *denom => true, + BodyPart::MoreThanProportion { nom, denom } if *nom * 2 >= *denom => true, + _ => false, + } + } +} + +impl TryFrom for BodyPart { + type Error = (); + fn try_from(value: OldBodyPart) -> Result { + use OldBodyPart::*; + Ok(match value { + Voice => Self::Voice, + Members { count } => Self::Members { count }, + Fraction { nom, denom } => Self::Fraction { nom, denom }, + AtLeastProportion { nom, denom } => Self::AtLeastProportion { nom, denom }, + MoreThanProportion { nom, denom } => Self::MoreThanProportion { nom, denom }, + }) + } +} + +/// A single item in a path to describe the relative location of a consensus system. +/// +/// Each item assumes a pre-existing location as its context and is defined in terms of it. +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen, +)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum Junction { + /// An indexed parachain belonging to and operated by the context. + /// + /// Generally used when the context is a Polkadot Relay-chain. + Parachain(#[codec(compact)] u32), + /// A 32-byte identifier for an account of a specific network that is respected as a sovereign endpoint within + /// the context. + /// + /// Generally used when the context is a Substrate-based chain. + AccountId32 { network: Option, id: [u8; 32] }, + /// An 8-byte index for an account of a specific network that is respected as a sovereign endpoint within + /// the context. + /// + /// May be used when the context is a Frame-based chain and includes e.g. an indices pallet. + AccountIndex64 { + network: Option, + #[codec(compact)] + index: u64, + }, + /// A 20-byte identifier for an account of a specific network that is respected as a sovereign endpoint within + /// the context. + /// + /// May be used when the context is an Ethereum or Bitcoin chain or smart-contract. + AccountKey20 { network: Option, key: [u8; 20] }, + /// An instanced, indexed pallet that forms a constituent part of the context. + /// + /// Generally used when the context is a Frame-based chain. + PalletInstance(u8), + /// A non-descript index within the context location. + /// + /// Usage will vary widely owing to its generality. + /// + /// NOTE: Try to avoid using this and instead use a more specific item. + GeneralIndex(#[codec(compact)] u128), + /// A nondescript 128-byte datum acting as a key within the context location. + /// + /// Usage will vary widely owing to its generality. + /// + /// NOTE: Try to avoid using this and instead use a more specific item. + GeneralKey([u8; 32]), + /// The unambiguous child. + /// + /// Not currently used except as a fallback when deriving context. + OnlyChild, + /// A pluralistic body existing within consensus. + /// + /// Typical to be used to represent a governance origin of a chain, but could in principle be used to represent + /// things such as multisigs also. + Plurality { id: BodyId, part: BodyPart }, + /// A global network capable of externalizing its own consensus. This is not generally + /// meaningful outside of the universal level. + GlobalConsensus(NetworkId), +} + +impl From for Junction { + fn from(n: NetworkId) -> Self { + Self::GlobalConsensus(n) + } +} + +impl From<[u8; 32]> for Junction { + fn from(id: [u8; 32]) -> Self { + Self::AccountId32 { network: None, id } + } +} + +impl From<[u8; 20]> for Junction { + fn from(key: [u8; 20]) -> Self { + Self::AccountKey20 { network: None, key } + } +} + +impl From for Junction { + fn from(index: u64) -> Self { + Self::AccountIndex64 { network: None, index } + } +} + +impl From for Junction { + fn from(id: u128) -> Self { + Self::GeneralIndex(id) + } +} + +impl TryFrom for Junction { + type Error = (); + fn try_from(value: OldJunction) -> Result { + use OldJunction::*; + Ok(match value { + Parachain(id) => Self::Parachain(id), + AccountId32 { network, id } => Self::AccountId32 { network: network.into(), id }, + AccountIndex64 { network, index } => + Self::AccountIndex64 { network: network.into(), index }, + AccountKey20 { network, key } => Self::AccountKey20 { network: network.into(), key }, + PalletInstance(index) => Self::PalletInstance(index), + GeneralIndex(id) => Self::GeneralIndex(id), + GeneralKey(_key) => return Err(()), + OnlyChild => Self::OnlyChild, + Plurality { id, part } => + Self::Plurality { id: id.try_into()?, part: part.try_into()? }, + }) + } +} + +impl Junction { + /// Convert `self` into a `MultiLocation` containing 0 parents. + /// + /// Similar to `Into::into`, except that this method can be used in a const evaluation context. + pub const fn into_location(self) -> MultiLocation { + MultiLocation { parents: 0, interior: Junctions::X1(self) } + } + + /// Convert `self` into a `MultiLocation` containing `n` parents. + /// + /// Similar to `Self::into_location`, with the added ability to specify the number of parent junctions. + pub const fn into_exterior(self, n: u8) -> MultiLocation { + MultiLocation { parents: n, interior: Junctions::X1(self) } + } + + /// Convert `self` into a `VersionedMultiLocation` containing 0 parents. + /// + /// Similar to `Into::into`, except that this method can be used in a const evaluation context. + pub const fn into_versioned(self) -> VersionedMultiLocation { + self.into_location().into_versioned() + } + + /// Remove the `NetworkId` value. + pub fn remove_network_id(&mut self) { + use Junction::*; + match self { + AccountId32 { ref mut network, .. } | + AccountIndex64 { ref mut network, .. } | + AccountKey20 { ref mut network, .. } => *network = None, + _ => {}, + } + } +} diff --git a/xcm/src/v3/junctions.rs b/xcm/src/v3/junctions.rs new file mode 100644 index 000000000000..d596489b209e --- /dev/null +++ b/xcm/src/v3/junctions.rs @@ -0,0 +1,708 @@ +// Copyright 2020-2021 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! XCM `Junctions`/`InteriorMultiLocation` datatype. + +use super::{Junction, MultiLocation, NetworkId}; +use core::{convert::TryFrom, mem, result}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +/// Maximum number of `Junction`s that a `Junctions` can contain. +pub(crate) const MAX_JUNCTIONS: usize = 8; + +/// Non-parent junctions that can be constructed, up to the length of 8. This specific `Junctions` +/// implementation uses a Rust `enum` in order to make pattern matching easier. +/// +/// Parent junctions cannot be constructed with this type. Refer to `MultiLocation` for +/// instructions on constructing parent junctions. +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen, +)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum Junctions { + /// The interpreting consensus system. + Here, + /// A relative path comprising 1 junction. + X1(Junction), + /// A relative path comprising 2 junctions. + X2(Junction, Junction), + /// A relative path comprising 3 junctions. + X3(Junction, Junction, Junction), + /// A relative path comprising 4 junctions. + X4(Junction, Junction, Junction, Junction), + /// A relative path comprising 5 junctions. + X5(Junction, Junction, Junction, Junction, Junction), + /// A relative path comprising 6 junctions. + X6(Junction, Junction, Junction, Junction, Junction, Junction), + /// A relative path comprising 7 junctions. + X7(Junction, Junction, Junction, Junction, Junction, Junction, Junction), + /// A relative path comprising 8 junctions. + X8(Junction, Junction, Junction, Junction, Junction, Junction, Junction, Junction), +} + +pub struct JunctionsIterator(Junctions); +impl Iterator for JunctionsIterator { + type Item = Junction; + fn next(&mut self) -> Option { + self.0.take_first() + } +} + +impl DoubleEndedIterator for JunctionsIterator { + fn next_back(&mut self) -> Option { + self.0.take_last() + } +} + +pub struct JunctionsRefIterator<'a> { + junctions: &'a Junctions, + next: usize, + back: usize, +} + +impl<'a> Iterator for JunctionsRefIterator<'a> { + type Item = &'a Junction; + fn next(&mut self) -> Option<&'a Junction> { + if self.next.saturating_add(self.back) >= self.junctions.len() { + return None + } + + let result = self.junctions.at(self.next); + self.next += 1; + result + } +} + +impl<'a> DoubleEndedIterator for JunctionsRefIterator<'a> { + fn next_back(&mut self) -> Option<&'a Junction> { + let next_back = self.back.saturating_add(1); + // checked_sub here, because if the result is less than 0, we end iteration + let index = self.junctions.len().checked_sub(next_back)?; + if self.next > index { + return None + } + self.back = next_back; + + self.junctions.at(index) + } +} +impl<'a> IntoIterator for &'a Junctions { + type Item = &'a Junction; + type IntoIter = JunctionsRefIterator<'a>; + fn into_iter(self) -> Self::IntoIter { + JunctionsRefIterator { junctions: self, next: 0, back: 0 } + } +} + +impl IntoIterator for Junctions { + type Item = Junction; + type IntoIter = JunctionsIterator; + fn into_iter(self) -> Self::IntoIter { + JunctionsIterator(self) + } +} + +impl Junctions { + /// Convert `self` into a `MultiLocation` containing 0 parents. + /// + /// Similar to `Into::into`, except that this method can be used in a const evaluation context. + pub const fn into_location(self) -> MultiLocation { + MultiLocation { parents: 0, interior: self } + } + + /// Convert `self` into a `MultiLocation` containing `n` parents. + /// + /// Similar to `Self::into_location`, with the added ability to specify the number of parent junctions. + pub const fn into_exterior(self, n: u8) -> MultiLocation { + MultiLocation { parents: n, interior: self } + } + + /// Remove the `NetworkId` value in any `Junction`s. + pub fn remove_network_id(&mut self) { + self.for_each_mut(Junction::remove_network_id); + } + + /// Treating `self` as the universal context, return the location of the local consensus system + /// from the point of view of the given `target`. + pub fn invert_target(mut self, target: &MultiLocation) -> Result { + let mut junctions = Self::Here; + for _ in 0..target.parent_count() { + junctions = junctions + .pushed_front_with(self.take_last().unwrap_or(Junction::OnlyChild)) + .map_err(|_| ())?; + } + let parents = target.interior().len() as u8; + Ok(MultiLocation::new(parents, junctions)) + } + + /// Execute a function `f` on every junction. We use this since we cannot implement a mutable + /// `Iterator` without unsafe code. + pub fn for_each_mut(&mut self, mut x: impl FnMut(&mut Junction)) { + match self { + Junctions::Here => {}, + Junctions::X1(a) => { + x(a); + }, + Junctions::X2(a, b) => { + x(a); + x(b); + }, + Junctions::X3(a, b, c) => { + x(a); + x(b); + x(c); + }, + Junctions::X4(a, b, c, d) => { + x(a); + x(b); + x(c); + x(d); + }, + Junctions::X5(a, b, c, d, e) => { + x(a); + x(b); + x(c); + x(d); + x(e); + }, + Junctions::X6(a, b, c, d, e, f) => { + x(a); + x(b); + x(c); + x(d); + x(e); + x(f); + }, + Junctions::X7(a, b, c, d, e, f, g) => { + x(a); + x(b); + x(c); + x(d); + x(e); + x(f); + x(g); + }, + Junctions::X8(a, b, c, d, e, f, g, h) => { + x(a); + x(b); + x(c); + x(d); + x(e); + x(f); + x(g); + x(h); + }, + } + } + + /// Extract the network ID treating this value as a universal location. + /// + /// This will return an `Err` if the first item is not a `GlobalConsensus`, which would indicate + /// that this value is not a universal location. + pub fn global_consensus(&self) -> Result { + if let Some(Junction::GlobalConsensus(network)) = self.first() { + Ok(*network) + } else { + Err(()) + } + } + + /// Extract the network ID and the interior consensus location, treating this value as a + /// universal location. + /// + /// This will return an `Err` if the first item is not a `GlobalConsensus`, which would indicate + /// that this value is not a universal location. + pub fn split_global(self) -> Result<(NetworkId, Junctions), ()> { + match self.split_first() { + (location, Some(Junction::GlobalConsensus(network))) => Ok((network, location)), + _ => return Err(()), + } + } + + /// Treat `self` as a universal location and the context of `relative`, returning the universal + /// location of relative. + /// + /// This will return an error if `relative` has as many (or more) parents than there are + /// junctions in `self`, implying that relative refers into a different global consensus. + pub fn within_global(mut self, relative: MultiLocation) -> Result { + if self.len() <= relative.parents as usize { + return Err(()) + } + for _ in 0..relative.parents { + self.take_last(); + } + for j in relative.interior { + self.push(j).map_err(|_| ())?; + } + Ok(self) + } + + /// Consumes `self` and returns how `viewer` would address it locally. + pub fn relative_to(mut self, viewer: &Junctions) -> MultiLocation { + let mut i = 0; + while match (self.first(), viewer.at(i)) { + (Some(x), Some(y)) => x == y, + _ => false, + } { + self = self.split_first().0; + // NOTE: Cannot overflow as loop can only iterate at most `MAX_JUNCTIONS` times. + i += 1; + } + // AUDIT NOTES: + // - above loop ensures that `i <= viewer.len()`. + // - `viewer.len()` is at most `MAX_JUNCTIONS`, so won't overflow a `u8`. + MultiLocation { parents: (viewer.len() - i) as u8, interior: self } + } + + /// Returns first junction, or `None` if the location is empty. + pub fn first(&self) -> Option<&Junction> { + match &self { + Junctions::Here => None, + Junctions::X1(ref a) => Some(a), + Junctions::X2(ref a, ..) => Some(a), + Junctions::X3(ref a, ..) => Some(a), + Junctions::X4(ref a, ..) => Some(a), + Junctions::X5(ref a, ..) => Some(a), + Junctions::X6(ref a, ..) => Some(a), + Junctions::X7(ref a, ..) => Some(a), + Junctions::X8(ref a, ..) => Some(a), + } + } + + /// Returns last junction, or `None` if the location is empty. + pub fn last(&self) -> Option<&Junction> { + match &self { + Junctions::Here => None, + Junctions::X1(ref a) => Some(a), + Junctions::X2(.., ref a) => Some(a), + Junctions::X3(.., ref a) => Some(a), + Junctions::X4(.., ref a) => Some(a), + Junctions::X5(.., ref a) => Some(a), + Junctions::X6(.., ref a) => Some(a), + Junctions::X7(.., ref a) => Some(a), + Junctions::X8(.., ref a) => Some(a), + } + } + + /// Splits off the first junction, returning the remaining suffix (first item in tuple) and the first element + /// (second item in tuple) or `None` if it was empty. + pub fn split_first(self) -> (Junctions, Option) { + match self { + Junctions::Here => (Junctions::Here, None), + Junctions::X1(a) => (Junctions::Here, Some(a)), + Junctions::X2(a, b) => (Junctions::X1(b), Some(a)), + Junctions::X3(a, b, c) => (Junctions::X2(b, c), Some(a)), + Junctions::X4(a, b, c, d) => (Junctions::X3(b, c, d), Some(a)), + Junctions::X5(a, b, c, d, e) => (Junctions::X4(b, c, d, e), Some(a)), + Junctions::X6(a, b, c, d, e, f) => (Junctions::X5(b, c, d, e, f), Some(a)), + Junctions::X7(a, b, c, d, e, f, g) => (Junctions::X6(b, c, d, e, f, g), Some(a)), + Junctions::X8(a, b, c, d, e, f, g, h) => (Junctions::X7(b, c, d, e, f, g, h), Some(a)), + } + } + + /// Splits off the last junction, returning the remaining prefix (first item in tuple) and the last element + /// (second item in tuple) or `None` if it was empty. + pub fn split_last(self) -> (Junctions, Option) { + match self { + Junctions::Here => (Junctions::Here, None), + Junctions::X1(a) => (Junctions::Here, Some(a)), + Junctions::X2(a, b) => (Junctions::X1(a), Some(b)), + Junctions::X3(a, b, c) => (Junctions::X2(a, b), Some(c)), + Junctions::X4(a, b, c, d) => (Junctions::X3(a, b, c), Some(d)), + Junctions::X5(a, b, c, d, e) => (Junctions::X4(a, b, c, d), Some(e)), + Junctions::X6(a, b, c, d, e, f) => (Junctions::X5(a, b, c, d, e), Some(f)), + Junctions::X7(a, b, c, d, e, f, g) => (Junctions::X6(a, b, c, d, e, f), Some(g)), + Junctions::X8(a, b, c, d, e, f, g, h) => (Junctions::X7(a, b, c, d, e, f, g), Some(h)), + } + } + + /// Removes the first element from `self`, returning it (or `None` if it was empty). + pub fn take_first(&mut self) -> Option { + let mut d = Junctions::Here; + mem::swap(&mut *self, &mut d); + let (tail, head) = d.split_first(); + *self = tail; + head + } + + /// Removes the last element from `self`, returning it (or `None` if it was empty). + pub fn take_last(&mut self) -> Option { + let mut d = Junctions::Here; + mem::swap(&mut *self, &mut d); + let (head, tail) = d.split_last(); + *self = head; + tail + } + + /// Mutates `self` to be appended with `new` or returns an `Err` with `new` if would overflow. + pub fn push(&mut self, new: impl Into) -> result::Result<(), Junction> { + let new = new.into(); + let mut dummy = Junctions::Here; + mem::swap(self, &mut dummy); + match dummy.pushed_with(new) { + Ok(s) => { + *self = s; + Ok(()) + }, + Err((s, j)) => { + *self = s; + Err(j) + }, + } + } + + /// Mutates `self` to be prepended with `new` or returns an `Err` with `new` if would overflow. + pub fn push_front(&mut self, new: impl Into) -> result::Result<(), Junction> { + let new = new.into(); + let mut dummy = Junctions::Here; + mem::swap(self, &mut dummy); + match dummy.pushed_front_with(new) { + Ok(s) => { + *self = s; + Ok(()) + }, + Err((s, j)) => { + *self = s; + Err(j) + }, + } + } + + /// Consumes `self` and returns a `Junctions` suffixed with `new`, or an `Err` with the + /// original value of `self` and `new` in case of overflow. + pub fn pushed_with(self, new: impl Into) -> result::Result { + let new = new.into(); + Ok(match self { + Junctions::Here => Junctions::X1(new), + Junctions::X1(a) => Junctions::X2(a, new), + Junctions::X2(a, b) => Junctions::X3(a, b, new), + Junctions::X3(a, b, c) => Junctions::X4(a, b, c, new), + Junctions::X4(a, b, c, d) => Junctions::X5(a, b, c, d, new), + Junctions::X5(a, b, c, d, e) => Junctions::X6(a, b, c, d, e, new), + Junctions::X6(a, b, c, d, e, f) => Junctions::X7(a, b, c, d, e, f, new), + Junctions::X7(a, b, c, d, e, f, g) => Junctions::X8(a, b, c, d, e, f, g, new), + s => Err((s, new))?, + }) + } + + /// Consumes `self` and returns a `Junctions` prefixed with `new`, or an `Err` with the + /// original value of `self` and `new` in case of overflow. + pub fn pushed_front_with( + self, + new: impl Into, + ) -> result::Result { + let new = new.into(); + Ok(match self { + Junctions::Here => Junctions::X1(new), + Junctions::X1(a) => Junctions::X2(new, a), + Junctions::X2(a, b) => Junctions::X3(new, a, b), + Junctions::X3(a, b, c) => Junctions::X4(new, a, b, c), + Junctions::X4(a, b, c, d) => Junctions::X5(new, a, b, c, d), + Junctions::X5(a, b, c, d, e) => Junctions::X6(new, a, b, c, d, e), + Junctions::X6(a, b, c, d, e, f) => Junctions::X7(new, a, b, c, d, e, f), + Junctions::X7(a, b, c, d, e, f, g) => Junctions::X8(new, a, b, c, d, e, f, g), + s => Err((s, new))?, + }) + } + + /// Mutate `self` so that it is suffixed with `suffix`. + /// + /// Does not modify `self` and returns `Err` with `suffix` in case of overflow. + /// + /// # Example + /// ```rust + /// # use xcm::v3::{Junctions::*, Junction::*, MultiLocation}; + /// # fn main() { + /// let mut m = X1(Parachain(21)); + /// assert_eq!(m.append_with(X1(PalletInstance(3))), Ok(())); + /// assert_eq!(m, X2(Parachain(21), PalletInstance(3))); + /// # } + /// ``` + pub fn append_with(&mut self, suffix: impl Into) -> Result<(), Junctions> { + let suffix = suffix.into(); + if self.len().saturating_add(suffix.len()) > MAX_JUNCTIONS { + return Err(suffix) + } + for j in suffix.into_iter() { + self.push(j).expect("Already checked the sum of the len()s; qed") + } + Ok(()) + } + + /// Returns the number of junctions in `self`. + pub const fn len(&self) -> usize { + match &self { + Junctions::Here => 0, + Junctions::X1(..) => 1, + Junctions::X2(..) => 2, + Junctions::X3(..) => 3, + Junctions::X4(..) => 4, + Junctions::X5(..) => 5, + Junctions::X6(..) => 6, + Junctions::X7(..) => 7, + Junctions::X8(..) => 8, + } + } + + /// Returns the junction at index `i`, or `None` if the location doesn't contain that many elements. + pub fn at(&self, i: usize) -> Option<&Junction> { + Some(match (i, self) { + (0, Junctions::X1(ref a)) => a, + (0, Junctions::X2(ref a, ..)) => a, + (0, Junctions::X3(ref a, ..)) => a, + (0, Junctions::X4(ref a, ..)) => a, + (0, Junctions::X5(ref a, ..)) => a, + (0, Junctions::X6(ref a, ..)) => a, + (0, Junctions::X7(ref a, ..)) => a, + (0, Junctions::X8(ref a, ..)) => a, + (1, Junctions::X2(_, ref a)) => a, + (1, Junctions::X3(_, ref a, ..)) => a, + (1, Junctions::X4(_, ref a, ..)) => a, + (1, Junctions::X5(_, ref a, ..)) => a, + (1, Junctions::X6(_, ref a, ..)) => a, + (1, Junctions::X7(_, ref a, ..)) => a, + (1, Junctions::X8(_, ref a, ..)) => a, + (2, Junctions::X3(_, _, ref a)) => a, + (2, Junctions::X4(_, _, ref a, ..)) => a, + (2, Junctions::X5(_, _, ref a, ..)) => a, + (2, Junctions::X6(_, _, ref a, ..)) => a, + (2, Junctions::X7(_, _, ref a, ..)) => a, + (2, Junctions::X8(_, _, ref a, ..)) => a, + (3, Junctions::X4(_, _, _, ref a)) => a, + (3, Junctions::X5(_, _, _, ref a, ..)) => a, + (3, Junctions::X6(_, _, _, ref a, ..)) => a, + (3, Junctions::X7(_, _, _, ref a, ..)) => a, + (3, Junctions::X8(_, _, _, ref a, ..)) => a, + (4, Junctions::X5(_, _, _, _, ref a)) => a, + (4, Junctions::X6(_, _, _, _, ref a, ..)) => a, + (4, Junctions::X7(_, _, _, _, ref a, ..)) => a, + (4, Junctions::X8(_, _, _, _, ref a, ..)) => a, + (5, Junctions::X6(_, _, _, _, _, ref a)) => a, + (5, Junctions::X7(_, _, _, _, _, ref a, ..)) => a, + (5, Junctions::X8(_, _, _, _, _, ref a, ..)) => a, + (6, Junctions::X7(_, _, _, _, _, _, ref a)) => a, + (6, Junctions::X8(_, _, _, _, _, _, ref a, ..)) => a, + (7, Junctions::X8(_, _, _, _, _, _, _, ref a)) => a, + _ => return None, + }) + } + + /// Returns a mutable reference to the junction at index `i`, or `None` if the location doesn't contain that many + /// elements. + pub fn at_mut(&mut self, i: usize) -> Option<&mut Junction> { + Some(match (i, self) { + (0, Junctions::X1(ref mut a)) => a, + (0, Junctions::X2(ref mut a, ..)) => a, + (0, Junctions::X3(ref mut a, ..)) => a, + (0, Junctions::X4(ref mut a, ..)) => a, + (0, Junctions::X5(ref mut a, ..)) => a, + (0, Junctions::X6(ref mut a, ..)) => a, + (0, Junctions::X7(ref mut a, ..)) => a, + (0, Junctions::X8(ref mut a, ..)) => a, + (1, Junctions::X2(_, ref mut a)) => a, + (1, Junctions::X3(_, ref mut a, ..)) => a, + (1, Junctions::X4(_, ref mut a, ..)) => a, + (1, Junctions::X5(_, ref mut a, ..)) => a, + (1, Junctions::X6(_, ref mut a, ..)) => a, + (1, Junctions::X7(_, ref mut a, ..)) => a, + (1, Junctions::X8(_, ref mut a, ..)) => a, + (2, Junctions::X3(_, _, ref mut a)) => a, + (2, Junctions::X4(_, _, ref mut a, ..)) => a, + (2, Junctions::X5(_, _, ref mut a, ..)) => a, + (2, Junctions::X6(_, _, ref mut a, ..)) => a, + (2, Junctions::X7(_, _, ref mut a, ..)) => a, + (2, Junctions::X8(_, _, ref mut a, ..)) => a, + (3, Junctions::X4(_, _, _, ref mut a)) => a, + (3, Junctions::X5(_, _, _, ref mut a, ..)) => a, + (3, Junctions::X6(_, _, _, ref mut a, ..)) => a, + (3, Junctions::X7(_, _, _, ref mut a, ..)) => a, + (3, Junctions::X8(_, _, _, ref mut a, ..)) => a, + (4, Junctions::X5(_, _, _, _, ref mut a)) => a, + (4, Junctions::X6(_, _, _, _, ref mut a, ..)) => a, + (4, Junctions::X7(_, _, _, _, ref mut a, ..)) => a, + (4, Junctions::X8(_, _, _, _, ref mut a, ..)) => a, + (5, Junctions::X6(_, _, _, _, _, ref mut a)) => a, + (5, Junctions::X7(_, _, _, _, _, ref mut a, ..)) => a, + (5, Junctions::X8(_, _, _, _, _, ref mut a, ..)) => a, + (6, Junctions::X7(_, _, _, _, _, _, ref mut a)) => a, + (6, Junctions::X8(_, _, _, _, _, _, ref mut a, ..)) => a, + (7, Junctions::X8(_, _, _, _, _, _, _, ref mut a)) => a, + _ => return None, + }) + } + + /// Returns a reference iterator over the junctions. + pub fn iter(&self) -> JunctionsRefIterator { + JunctionsRefIterator { junctions: self, next: 0, back: 0 } + } + + /// Ensures that self begins with `prefix` and that it has a single `Junction` item following. + /// If so, returns a reference to this `Junction` item. + /// + /// # Example + /// ```rust + /// # use xcm::v3::{Junctions::*, Junction::*}; + /// # fn main() { + /// let mut m = X3(Parachain(2), PalletInstance(3), OnlyChild); + /// assert_eq!(m.match_and_split(&X2(Parachain(2), PalletInstance(3))), Some(&OnlyChild)); + /// assert_eq!(m.match_and_split(&X1(Parachain(2))), None); + /// # } + /// ``` + pub fn match_and_split(&self, prefix: &Junctions) -> Option<&Junction> { + if prefix.len() + 1 != self.len() { + return None + } + for i in 0..prefix.len() { + if prefix.at(i) != self.at(i) { + return None + } + } + return self.at(prefix.len()) + } + + pub fn starts_with(&self, prefix: &Junctions) -> bool { + prefix.len() <= self.len() && prefix.iter().zip(self.iter()).all(|(x, y)| x == y) + } +} + +impl TryFrom for Junctions { + type Error = MultiLocation; + fn try_from(x: MultiLocation) -> result::Result { + if x.parents > 0 { + Err(x) + } else { + Ok(x.interior) + } + } +} + +impl> From for Junctions { + fn from(x: T) -> Self { + Self::X1(x.into()) + } +} + +impl From<[Junction; 0]> for Junctions { + fn from(_: [Junction; 0]) -> Self { + Self::Here + } +} + +impl From<()> for Junctions { + fn from(_: ()) -> Self { + Self::Here + } +} + +xcm_procedural::impl_conversion_functions_for_junctions_v3!(); + +#[cfg(test)] +mod tests { + use super::{super::prelude::*, *}; + + #[test] + fn inverting_works() { + let context: InteriorMultiLocation = (Parachain(1000), PalletInstance(42)).into(); + let target = (Parent, PalletInstance(69)).into(); + let expected = (Parent, PalletInstance(42)).into(); + let inverted = context.invert_target(&target).unwrap(); + assert_eq!(inverted, expected); + + let context: InteriorMultiLocation = + (Parachain(1000), PalletInstance(42), GeneralIndex(1)).into(); + let target = (Parent, Parent, PalletInstance(69), GeneralIndex(2)).into(); + let expected = (Parent, Parent, PalletInstance(42), GeneralIndex(1)).into(); + let inverted = context.invert_target(&target).unwrap(); + assert_eq!(inverted, expected); + } + + #[test] + fn relative_to_works() { + use Junctions::*; + use NetworkId::*; + assert_eq!(X1(Polkadot.into()).relative_to(&X1(Kusama.into())), (Parent, Polkadot).into()); + let base = X3(Kusama.into(), Parachain(1), PalletInstance(1)); + + // Ancestors. + assert_eq!(Here.relative_to(&base), (Parent, Parent, Parent).into()); + assert_eq!(X1(Kusama.into()).relative_to(&base), (Parent, Parent).into()); + assert_eq!(X2(Kusama.into(), Parachain(1)).relative_to(&base), (Parent,).into()); + assert_eq!( + X3(Kusama.into(), Parachain(1), PalletInstance(1)).relative_to(&base), + Here.into() + ); + + // Ancestors with one child. + assert_eq!( + X1(Polkadot.into()).relative_to(&base), + (Parent, Parent, Parent, Polkadot).into() + ); + assert_eq!( + X2(Kusama.into(), Parachain(2)).relative_to(&base), + (Parent, Parent, Parachain(2)).into() + ); + assert_eq!( + X3(Kusama.into(), Parachain(1), PalletInstance(2)).relative_to(&base), + (Parent, PalletInstance(2)).into() + ); + assert_eq!( + X4(Kusama.into(), Parachain(1), PalletInstance(1), [1u8; 32].into()).relative_to(&base), + ([1u8; 32],).into() + ); + + // Ancestors with grandchildren. + assert_eq!( + X2(Polkadot.into(), Parachain(1)).relative_to(&base), + (Parent, Parent, Parent, Polkadot, Parachain(1)).into() + ); + assert_eq!( + X3(Kusama.into(), Parachain(2), PalletInstance(1)).relative_to(&base), + (Parent, Parent, Parachain(2), PalletInstance(1)).into() + ); + assert_eq!( + X4(Kusama.into(), Parachain(1), PalletInstance(2), [1u8; 32].into()).relative_to(&base), + (Parent, PalletInstance(2), [1u8; 32]).into() + ); + assert_eq!( + X5(Kusama.into(), Parachain(1), PalletInstance(1), [1u8; 32].into(), 1u128.into()) + .relative_to(&base), + ([1u8; 32], 1u128).into() + ); + } + + #[test] + fn global_consensus_works() { + use Junctions::*; + use NetworkId::*; + assert_eq!(X1(Polkadot.into()).global_consensus(), Ok(Polkadot)); + assert_eq!(X2(Kusama.into(), 1u64.into()).global_consensus(), Ok(Kusama)); + assert_eq!(Here.global_consensus(), Err(())); + assert_eq!(X1(1u64.into()).global_consensus(), Err(())); + assert_eq!(X2(1u64.into(), Kusama.into()).global_consensus(), Err(())); + } + + #[test] + fn test_conversion() { + use super::{Junction::*, Junctions::*, NetworkId::*}; + let x: Junctions = GlobalConsensus(Polkadot).into(); + assert_eq!(x, X1(GlobalConsensus(Polkadot))); + let x: Junctions = Polkadot.into(); + assert_eq!(x, X1(GlobalConsensus(Polkadot))); + let x: Junctions = (Polkadot, Kusama).into(); + assert_eq!(x, X2(GlobalConsensus(Polkadot), GlobalConsensus(Kusama))); + } +} diff --git a/xcm/src/v3/mod.rs b/xcm/src/v3/mod.rs new file mode 100644 index 000000000000..7f55f27fad28 --- /dev/null +++ b/xcm/src/v3/mod.rs @@ -0,0 +1,1435 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate 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. + +// Substrate 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 Cumulus. If not, see . + +//! Version 3 of the Cross-Consensus Message format data structures. + +use super::v2::{ + Instruction as OldInstruction, Response as OldResponse, WeightLimit as OldWeightLimit, + Xcm as OldXcm, +}; +use crate::{DoubleEncoded, GetWeight}; +use alloc::{vec, vec::Vec}; +use core::{ + convert::{TryFrom, TryInto}, + fmt::Debug, + result, +}; +use derivative::Derivative; +use parity_scale_codec::{self, Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +mod junction; +pub(crate) mod junctions; +mod multiasset; +mod multilocation; +mod traits; + +pub use junction::{BodyId, BodyPart, Junction, NetworkId}; +pub use junctions::Junctions; +pub use multiasset::{ + AssetId, AssetInstance, Fungibility, MultiAsset, MultiAssetFilter, MultiAssets, + WildFungibility, WildMultiAsset, +}; +pub use multilocation::{ + Ancestor, AncestorThen, InteriorMultiLocation, MultiLocation, Parent, ParentThen, +}; +pub use traits::{ + send_xcm, validate_send, Error, ExecuteXcm, Outcome, PreparedMessage, Result, SendError, + SendResult, SendXcm, Unwrappable, Weight, XcmHash, +}; +// These parts of XCM v2 are unchanged in XCM v3, and are re-imported here. +pub use super::v2::OriginKind; + +/// This module's XCM version. +pub const VERSION: super::Version = 3; + +/// An identifier for a query. +pub type QueryId = u64; + +#[derive(Derivative, Default, Encode, Decode, TypeInfo)] +#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +#[scale_info(bounds(), skip_type_params(Call))] +pub struct Xcm(pub Vec>); + +impl Xcm { + /// Create an empty instance. + pub fn new() -> Self { + Self(vec![]) + } + + /// Return `true` if no instructions are held in `self`. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Return the number of instructions held in `self`. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Return a reference to the inner value. + pub fn inner(&self) -> &[Instruction] { + &self.0 + } + + /// Return a mutable reference to the inner value. + pub fn inner_mut(&mut self) -> &mut Vec> { + &mut self.0 + } + + /// Consume and return the inner value. + pub fn into_inner(self) -> Vec> { + self.0 + } + + /// Return an iterator over references to the items. + pub fn iter(&self) -> impl Iterator> { + self.0.iter() + } + + /// Return an iterator over mutable references to the items. + pub fn iter_mut(&mut self) -> impl Iterator> { + self.0.iter_mut() + } + + /// Consume and return an iterator over the items. + pub fn into_iter(self) -> impl Iterator> { + self.0.into_iter() + } + + /// Consume and either return `self` if it contains some instructions, or if it's empty, then + /// instead return the result of `f`. + pub fn or_else(self, f: impl FnOnce() -> Self) -> Self { + if self.0.is_empty() { + f() + } else { + self + } + } + + /// Return the first instruction, if any. + pub fn first(&self) -> Option<&Instruction> { + self.0.first() + } + + /// Return the last instruction, if any. + pub fn last(&self) -> Option<&Instruction> { + self.0.last() + } + + /// Return the only instruction, contained in `Self`, iff only one exists (`None` otherwise). + pub fn only(&self) -> Option<&Instruction> { + if self.0.len() == 1 { + self.0.first() + } else { + None + } + } + + /// Return the only instruction, contained in `Self`, iff only one exists (returns `self` + /// otherwise). + pub fn into_only(mut self) -> core::result::Result, Self> { + if self.0.len() == 1 { + self.0.pop().ok_or(self) + } else { + Err(self) + } + } +} + +impl From>> for Xcm { + fn from(c: Vec>) -> Self { + Self(c) + } +} + +impl From> for Vec> { + fn from(c: Xcm) -> Self { + c.0 + } +} + +/// A prelude for importing all types typically used when interacting with XCM messages. +pub mod prelude { + mod contents { + pub use super::super::{ + send_xcm, validate_send, Ancestor, AncestorThen, + AssetId::{self, *}, + AssetInstance::{self, *}, + BodyId, BodyPart, Error as XcmError, ExecuteXcm, + Fungibility::{self, *}, + Instruction::*, + InteriorMultiLocation, + Junction::{self, *}, + Junctions::{self, *}, + MaybeErrorCode, MultiAsset, + MultiAssetFilter::{self, *}, + MultiAssets, MultiLocation, + NetworkId::{self, *}, + OriginKind, Outcome, PalletInfo, Parent, ParentThen, PreparedMessage, QueryId, + QueryResponseInfo, Response, Result as XcmResult, SendError, SendResult, SendXcm, + Unwrappable, + WeightLimit::{self, *}, + WildFungibility::{self, Fungible as WildFungible, NonFungible as WildNonFungible}, + WildMultiAsset::{self, *}, + XcmContext, XcmHash, XcmWeightInfo, VERSION as XCM_VERSION, + }; + } + pub use super::{Instruction, Xcm}; + pub use contents::*; + pub mod opaque { + pub use super::{ + super::opaque::{Instruction, Xcm}, + contents::*, + }; + } +} + +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)] +pub struct PalletInfo { + #[codec(compact)] + index: u32, + // TODO: Change to `BoundedVec` so `MaxEncodedLen` derive will work. + name: Vec, + // TODO: Change to `BoundedVec` so `MaxEncodedLen` derive will work. + module_name: Vec, + #[codec(compact)] + major: u32, + #[codec(compact)] + minor: u32, + #[codec(compact)] + patch: u32, +} + +const MAX_NAME_LEN: usize = 48; + +impl PalletInfo { + pub fn new( + index: u32, + name: Vec, + module_name: Vec, + major: u32, + minor: u32, + patch: u32, + ) -> result::Result { + if name.len() > MAX_NAME_LEN || module_name.len() > MAX_NAME_LEN { + return Err(Error::Overflow) + } + Ok(Self { index, name, module_name, major, minor, patch }) + } +} + +impl MaxEncodedLen for PalletInfo { + fn max_encoded_len() -> usize { + parity_scale_codec::Compact::::max_encoded_len() * 4 + (MAX_NAME_LEN + 1) * 2 + } +} + +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)] +pub enum MaybeErrorCode { + Success, + // TODO: Change to a `BoundedVec` so that deriving `MaxEncodedLen` works. + Error(Vec), + TruncatedError(Vec), +} + +/// Maximum size of the encoded error code coming from a `Dispatch` result, used for +/// `MaybeErrorCode`. This is not (yet) enforced, so it's just an indication of expectation. +const MAX_DISPATCH_ERROR_LEN: usize = 128; + +impl MaxEncodedLen for MaybeErrorCode { + fn max_encoded_len() -> usize { + MAX_DISPATCH_ERROR_LEN + 3 + } +} + +impl From> for MaybeErrorCode { + fn from(mut v: Vec) -> Self { + if v.len() <= MAX_DISPATCH_ERROR_LEN { + MaybeErrorCode::Error(v) + } else { + v.truncate(MAX_DISPATCH_ERROR_LEN); + MaybeErrorCode::TruncatedError(v) + } + } +} + +impl Default for MaybeErrorCode { + fn default() -> MaybeErrorCode { + MaybeErrorCode::Success + } +} + +/// Maximum number of pallets which we expect to be returned in `PalletsInfo`. +const MAX_PALLETS_INFO_LEN: usize = 64; + +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)] +pub struct VecPalletInfo(Vec); +impl TryFrom> for VecPalletInfo { + type Error = Error; + fn try_from(v: Vec) -> result::Result { + if v.len() > MAX_PALLETS_INFO_LEN { + return Err(Error::Overflow) + } + Ok(VecPalletInfo(v)) + } +} +impl MaxEncodedLen for VecPalletInfo { + fn max_encoded_len() -> usize { + PalletInfo::max_encoded_len() * MAX_PALLETS_INFO_LEN + } +} + +/// Response data to a query. +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] +pub enum Response { + /// No response. Serves as a neutral default. + Null, + /// Some assets. + Assets(MultiAssets), + /// The outcome of an XCM instruction. + ExecutionResult(Option<(u32, Error)>), + /// An XCM version. + Version(super::Version), + /// The index, instance name, pallet name and version of some pallets. + // TODO: Change to a `BoundedVec` so that deriving `MaxEncodedLen` works. + PalletsInfo(VecPalletInfo), + /// The status of a dispatch attempt using `Transact`. + DispatchResult(MaybeErrorCode), +} + +impl Default for Response { + fn default() -> Self { + Self::Null + } +} + +/// Information regarding the composition of a query response. +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)] +pub struct QueryResponseInfo { + /// The destination to which the query response message should be send. + pub destination: MultiLocation, + /// The `query_id` field of the `QueryResponse` message. + #[codec(compact)] + pub query_id: QueryId, + /// The `max_weight` field of the `QueryResponse` message. + pub max_weight: Weight, +} + +/// An optional weight limit. +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)] +pub enum WeightLimit { + /// No weight limit imposed. + Unlimited, + /// Weight limit imposed of the inner value. + Limited(Weight), +} + +impl From> for WeightLimit { + fn from(x: Option) -> Self { + match x { + Some(w) => WeightLimit::Limited(w), + None => WeightLimit::Unlimited, + } + } +} + +impl From for Option { + fn from(x: WeightLimit) -> Self { + match x { + WeightLimit::Limited(w) => Some(w), + WeightLimit::Unlimited => None, + } + } +} + +impl TryFrom for WeightLimit { + type Error = (); + fn try_from(x: OldWeightLimit) -> result::Result { + use OldWeightLimit::*; + match x { + Limited(w) => Ok(Self::Limited(Weight::from_parts(w, DEFAULT_PROOF_SIZE))), + Unlimited => Ok(Self::Unlimited), + } + } +} + +/// Contextual data pertaining to a specific list of XCM instructions. +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] +pub struct XcmContext { + /// The `MultiLocation` origin of the corresponding XCM. + pub origin: Option, + /// The hash of the XCM. + pub message_hash: XcmHash, + /// The topic of the XCM. + pub topic: Option<[u8; 32]>, +} + +impl XcmContext { + /// Constructor which sets the message hash to the supplied parameter and leaves the origin and + /// topic unset. + pub fn with_message_hash(message_hash: XcmHash) -> XcmContext { + XcmContext { origin: None, message_hash, topic: None } + } +} + +/// Cross-Consensus Message: A message from one consensus system to another. +/// +/// Consensus systems that may send and receive messages include blockchains and smart contracts. +/// +/// All messages are delivered from a known *origin*, expressed as a `MultiLocation`. +/// +/// This is the inner XCM format and is version-sensitive. Messages are typically passed using the outer +/// XCM format, known as `VersionedXcm`. +#[derive(Derivative, Encode, Decode, TypeInfo, xcm_procedural::XcmWeightInfoTrait)] +#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +#[scale_info(bounds(), skip_type_params(Call))] +pub enum Instruction { + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place them into the Holding + /// Register. + /// + /// - `assets`: The asset(s) to be withdrawn into holding. + /// + /// Kind: *Instruction*. + /// + /// Errors: + WithdrawAsset(MultiAssets), + + /// Asset(s) (`assets`) have been received into the ownership of this system on the `origin` + /// system and equivalent derivatives should be placed into the Holding Register. + /// + /// - `assets`: The asset(s) that are minted into holding. + /// + /// Safety: `origin` must be trusted to have received and be storing `assets` such that they + /// may later be withdrawn should this system send a corresponding message. + /// + /// Kind: *Trusted Indication*. + /// + /// Errors: + ReserveAssetDeposited(MultiAssets), + + /// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets should + /// be created and placed into the Holding Register. + /// + /// - `assets`: The asset(s) that are minted into the Holding Register. + /// + /// Safety: `origin` must be trusted to have irrevocably destroyed the corresponding `assets` + /// prior as a consequence of sending this message. + /// + /// Kind: *Trusted Indication*. + /// + /// Errors: + ReceiveTeleportedAsset(MultiAssets), + + /// Respond with information that the local system is expecting. + /// + /// - `query_id`: The identifier of the query that resulted in this message being sent. + /// - `response`: The message content. + /// - `max_weight`: The maximum weight that handling this response should take. + /// - `querier`: The location responsible for the initiation of the response, if there is one. + /// In general this will tend to be the same location as the receiver of this message. + /// NOTE: As usual, this is interpreted from the perspective of the receiving consensus + /// system. + /// + /// Safety: Since this is information only, there are no immediate concerns. However, it should + /// be remembered that even if the Origin behaves reasonably, it can always be asked to make + /// a response to a third-party chain who may or may not be expecting the response. Therefore + /// the `querier` should be checked to match the expected value. + /// + /// Kind: *Information*. + /// + /// Errors: + QueryResponse { + #[codec(compact)] + query_id: QueryId, + response: Response, + max_weight: Weight, + querier: Option, + }, + + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets + /// under the ownership of `beneficiary`. + /// + /// - `assets`: The asset(s) to be withdrawn. + /// - `beneficiary`: The new owner for the assets. + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction*. + /// + /// Errors: + TransferAsset { assets: MultiAssets, beneficiary: MultiLocation }, + + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets + /// under the ownership of `dest` within this consensus system (i.e. its sovereign account). + /// + /// Send an onward XCM message to `dest` of `ReserveAssetDeposited` with the wantn + /// `xcm`. + /// + /// - `assets`: The asset(s) to be withdrawn. + /// - `dest`: The location whose sovereign account will own the assets and thus the effective + /// beneficiary for the assets and the notification target for the reserve asset deposit + /// message. + /// - `xcm`: The instructions that should follow the `ReserveAssetDeposited` + /// instruction, which is sent onwards to `dest`. + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction*. + /// + /// Errors: + TransferReserveAsset { assets: MultiAssets, dest: MultiLocation, xcm: Xcm<()> }, + + /// Apply the encoded transaction `call`, whose dispatch-origin should be `origin` as expressed + /// by the kind of origin `origin_kind`. + /// + /// The Transact Status Register is set according to the result of dispatching the call. + /// + /// - `origin_kind`: The means of expressing the message origin as a dispatch origin. + /// - `require_weight_at_most`: The weight of `call`; this should be at least the chain's + /// calculated weight and will be used in the weight determination arithmetic. + /// - `call`: The encoded transaction to be applied. + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction*. + /// + /// Errors: + Transact { origin_kind: OriginKind, require_weight_at_most: Weight, call: DoubleEncoded }, + + /// A message to notify about a new incoming HRMP channel. This message is meant to be sent by the + /// relay-chain to a para. + /// + /// - `sender`: The sender in the to-be opened channel. Also, the initiator of the channel opening. + /// - `max_message_size`: The maximum size of a message proposed by the sender. + /// - `max_capacity`: The maximum number of messages that can be queued in the channel. + /// + /// Safety: The message should originate directly from the relay-chain. + /// + /// Kind: *System Notification* + HrmpNewChannelOpenRequest { + #[codec(compact)] + sender: u32, + #[codec(compact)] + max_message_size: u32, + #[codec(compact)] + max_capacity: u32, + }, + + /// A message to notify about that a previously sent open channel request has been accepted by + /// the recipient. That means that the channel will be opened during the next relay-chain session + /// change. This message is meant to be sent by the relay-chain to a para. + /// + /// Safety: The message should originate directly from the relay-chain. + /// + /// Kind: *System Notification* + /// + /// Errors: + HrmpChannelAccepted { + // NOTE: We keep this as a structured item to a) keep it consistent with the other Hrmp + // items; and b) because the field's meaning is not obvious/mentioned from the item name. + #[codec(compact)] + recipient: u32, + }, + + /// A message to notify that the other party in an open channel decided to close it. In particular, + /// `initiator` is going to close the channel opened from `sender` to the `recipient`. The close + /// will be enacted at the next relay-chain session change. This message is meant to be sent by + /// the relay-chain to a para. + /// + /// Safety: The message should originate directly from the relay-chain. + /// + /// Kind: *System Notification* + /// + /// Errors: + HrmpChannelClosing { + #[codec(compact)] + initiator: u32, + #[codec(compact)] + sender: u32, + #[codec(compact)] + recipient: u32, + }, + + /// Clear the origin. + /// + /// This may be used by the XCM author to ensure that later instructions cannot command the + /// authority of the origin (e.g. if they are being relayed from an untrusted source, as often + /// the case with `ReserveAssetDeposited`). + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction*. + /// + /// Errors: + ClearOrigin, + + /// Mutate the origin to some interior location. + /// + /// Kind: *Instruction* + /// + /// Errors: + DescendOrigin(InteriorMultiLocation), + + /// Immediately report the contents of the Error Register to the wantn destination via XCM. + /// + /// A `QueryResponse` message of type `ExecutionOutcome` is sent to the described destination. + /// + /// - `response_info`: Information for making the response. + /// + /// Kind: *Instruction* + /// + /// Errors: + ReportError(QueryResponseInfo), + + /// Remove the asset(s) (`assets`) from the Holding Register and place equivalent assets under + /// the ownership of `beneficiary` within this consensus system. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `beneficiary`: The new owner for the assets. + /// + /// Kind: *Instruction* + /// + /// Errors: + DepositAsset { assets: MultiAssetFilter, beneficiary: MultiLocation }, + + /// Remove the asset(s) (`assets`) from the Holding Register and place equivalent assets under + /// the ownership of `dest` within this consensus system (i.e. deposit them into its sovereign + /// account). + /// + /// Send an onward XCM message to `dest` of `ReserveAssetDeposited` with the wantn `effects`. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `dest`: The location whose sovereign account will own the assets and thus the effective + /// beneficiary for the assets and the notification target for the reserve asset deposit + /// message. + /// - `xcm`: The orders that should follow the `ReserveAssetDeposited` instruction + /// which is sent onwards to `dest`. + /// + /// Kind: *Instruction* + /// + /// Errors: + DepositReserveAsset { assets: MultiAssetFilter, dest: MultiLocation, xcm: Xcm<()> }, + + /// Remove the asset(s) (`want`) from the Holding Register and replace them with alternative + /// assets. + /// + /// The minimum amount of assets to be received into the Holding Register for the order not to + /// fail may be stated. + /// + /// - `give`: The maximum amount of assets to remove from holding. + /// - `want`: The minimum amount of assets which `give` should be exchanged for. + /// - `maximal`: If `true`, then prefer to give as much as possible up to the limit of `give` + /// and receive accordingly more. If `false`, then prefer to give as little as possible in + /// order to receive as little as possible while receiving at least `want`. + /// + /// Kind: *Instruction* + /// + /// Errors: + ExchangeAsset { give: MultiAssetFilter, want: MultiAssets, maximal: bool }, + + /// Remove the asset(s) (`assets`) from holding and send a `WithdrawAsset` XCM message to a + /// reserve location. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `reserve`: A valid location that acts as a reserve for all asset(s) in `assets`. The + /// sovereign account of this consensus system *on the reserve location* will have appropriate + /// assets withdrawn and `effects` will be executed on them. There will typically be only one + /// valid location on any wantn asset/chain combination. + /// - `xcm`: The instructions to execute on the assets once withdrawn *on the reserve + /// location*. + /// + /// Kind: *Instruction* + /// + /// Errors: + InitiateReserveWithdraw { assets: MultiAssetFilter, reserve: MultiLocation, xcm: Xcm<()> }, + + /// Remove the asset(s) (`assets`) from holding and send a `ReceiveTeleportedAsset` XCM message + /// to a `dest` location. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `dest`: A valid location that respects teleports coming from this location. + /// - `xcm`: The instructions to execute on the assets once arrived *on the destination + /// location*. + /// + /// NOTE: The `dest` location *MUST* respect this origin as a valid teleportation origin for all + /// `assets`. If it does not, then the assets may be lost. + /// + /// Kind: *Instruction* + /// + /// Errors: + InitiateTeleport { assets: MultiAssetFilter, dest: MultiLocation, xcm: Xcm<()> }, + + /// Report to a wantn destination the contents of the Holding Register. + /// + /// A `QueryResponse` message of type `Assets` is sent to the described destination. + /// + /// - `response_info`: Information for making the response. + /// - `assets`: A filter for the assets that should be reported back. The assets reported back + /// will be, asset-wise, *the lesser of this value and the holding register*. No wildcards + /// will be used when reporting assets back. + /// + /// Kind: *Instruction* + /// + /// Errors: + ReportHolding { response_info: QueryResponseInfo, assets: MultiAssetFilter }, + + /// Pay for the execution of some XCM `xcm` and `orders` with up to `weight` + /// picoseconds of execution time, paying for this with up to `fees` from the Holding Register. + /// + /// - `fees`: The asset(s) to remove from the Holding Register to pay for fees. + /// - `weight_limit`: The maximum amount of weight to purchase; this must be at least the + /// expected maximum weight of the total XCM to be executed for the + /// `AllowTopLevelPaidExecutionFrom` barrier to allow the XCM be executed. + /// + /// Kind: *Instruction* + /// + /// Errors: + BuyExecution { fees: MultiAsset, weight_limit: WeightLimit }, + + /// Refund any surplus weight previously bought with `BuyExecution`. + /// + /// Kind: *Instruction* + /// + /// Errors: None. + RefundSurplus, + + /// Set the Error Handler Register. This is code that should be called in the case of an error + /// happening. + /// + /// An error occurring within execution of this code will _NOT_ result in the error register + /// being set, nor will an error handler be called due to it. The error handler and appendix + /// may each still be set. + /// + /// The apparent weight of this instruction is inclusive of the inner `Xcm`; the executing + /// weight however includes only the difference between the previous handler and the new + /// handler, which can reasonably be negative, which would result in a surplus. + /// + /// Kind: *Instruction* + /// + /// Errors: None. + SetErrorHandler(Xcm), + + /// Set the Appendix Register. This is code that should be called after code execution + /// (including the error handler if any) is finished. This will be called regardless of whether + /// an error occurred. + /// + /// Any error occurring due to execution of this code will result in the error register being + /// set, and the error handler (if set) firing. + /// + /// The apparent weight of this instruction is inclusive of the inner `Xcm`; the executing + /// weight however includes only the difference between the previous appendix and the new + /// appendix, which can reasonably be negative, which would result in a surplus. + /// + /// Kind: *Instruction* + /// + /// Errors: None. + SetAppendix(Xcm), + + /// Clear the Error Register. + /// + /// Kind: *Instruction* + /// + /// Errors: None. + ClearError, + + /// Create some assets which are being held on behalf of the origin. + /// + /// - `assets`: The assets which are to be claimed. This must match exactly with the assets + /// claimable by the origin of the ticket. + /// - `ticket`: The ticket of the asset; this is an abstract identifier to help locate the + /// asset. + /// + /// Kind: *Instruction* + /// + /// Errors: + ClaimAsset { assets: MultiAssets, ticket: MultiLocation }, + + /// Always throws an error of type `Trap`. + /// + /// Kind: *Instruction* + /// + /// Errors: + /// - `Trap`: All circumstances, whose inner value is the same as this item's inner value. + Trap(#[codec(compact)] u64), + + /// Ask the destination system to respond with the most recent version of XCM that they + /// support in a `QueryResponse` instruction. Any changes to this should also elicit similar + /// responses when they happen. + /// + /// - `query_id`: An identifier that will be replicated into the returned XCM message. + /// - `max_response_weight`: The maximum amount of weight that the `QueryResponse` item which + /// is sent as a reply may take to execute. NOTE: If this is unexpectedly large then the + /// response may not execute at all. + /// + /// Kind: *Instruction* + /// + /// Errors: *Fallible* + SubscribeVersion { + #[codec(compact)] + query_id: QueryId, + max_response_weight: Weight, + }, + + /// Cancel the effect of a previous `SubscribeVersion` instruction. + /// + /// Kind: *Instruction* + /// + /// Errors: *Fallible* + UnsubscribeVersion, + + /// Reduce Holding by up to the wantn assets. + /// + /// Holding is reduced by as much as possible up to the assets in the parameter. It is not an + /// error if the Holding does not contain the assets (to make this an error, use `ExpectAsset` + /// prior). + /// + /// Kind: *Instruction* + /// + /// Errors: *Infallible* + BurnAsset(MultiAssets), + + /// Throw an error if Holding does not contain at least the wantn assets. + /// + /// Kind: *Instruction* + /// + /// Errors: + /// - `ExpectationFalse`: If Holding Register does not contain the assets in the parameter. + ExpectAsset(MultiAssets), + + /// Ensure that the Origin Register equals some wantn value and throw an error if not. + /// + /// Kind: *Instruction* + /// + /// Errors: + /// - `ExpectationFalse`: If Origin Register is not equal to the parameter. + ExpectOrigin(Option), + + /// Ensure that the Error Register equals some wantn value and throw an error if not. + /// + /// Kind: *Instruction* + /// + /// Errors: + /// - `ExpectationFalse`: If the value of the Error Register is not equal to the parameter. + ExpectError(Option<(u32, Error)>), + + /// Query the existence of a particular pallet type. + /// + /// - `module_name`: The module name of the pallet to query. + /// - `response_info`: Information for making the response. + /// + /// Sends a `QueryResponse` to Origin whose data field `PalletsInfo` containing the information + /// of all pallets on the local chain whose name is equal to `name`. This is empty in the case + /// that the local chain is not based on Substrate Frame. + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction* + /// + /// Errors: *Fallible*. + QueryPallet { module_name: Vec, response_info: QueryResponseInfo }, + + /// Ensure that a particular pallet with a particular version exists. + /// + /// - `index: Compact`: The index which identifies the pallet. An error if no pallet exists at this index. + /// - `name: Vec`: Name which must be equal to the name of the pallet. + /// - `module_name: Vec`: Module name which must be equal to the name of the module in which the pallet exists. + /// - `crate_major: Compact`: Version number which must be equal to the major version of the crate which implements the pallet. + /// - `min_crate_minor: Compact`: Version number which must be at most the minor version of the crate which implements the pallet. + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction* + /// + /// Errors: + /// - `ExpectationFalse`: In case any of the expectations are broken. + ExpectPallet { + #[codec(compact)] + index: u32, + name: Vec, + module_name: Vec, + #[codec(compact)] + crate_major: u32, + #[codec(compact)] + min_crate_minor: u32, + }, + + /// Send a `QueryResponse` message containing the value of the Transact Status Register to some + /// destination. + /// + /// - `query_response_info`: The information needed for constructing and sending the + /// `QueryResponse` message. + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction* + /// + /// Errors: *Fallible*. + ReportTransactStatus(QueryResponseInfo), + + /// Set the Transact Status Register to its default, cleared, value. + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction* + /// + /// Errors: *Infallible*. + ClearTransactStatus, + + /// Set the Origin Register to be some child of the Universal Ancestor. + /// + /// Safety: Should only be usable if the Origin is trusted to represent the Universal Ancestor + /// child in general. In general, no Origin should be able to represent the Universal Ancestor + /// child which is the root of the local consensus system since it would by extension + /// allow it to act as any location within the local consensus. + /// + /// The `Junction` parameter should generally be a `GlobalConsensus` variant since it is only + /// these which are children of the Universal Ancestor. + /// + /// Kind: *Instruction* + /// + /// Errors: *Fallible*. + UniversalOrigin(Junction), + + /// Send a message on to Non-Local Consensus system. + /// + /// This will tend to utilize some extra-consensus mechanism, the obvious one being a bridge. + /// A fee may be charged; this may be determined based on the contents of `xcm`. It will be + /// taken from the Holding register. + /// + /// - `network`: The remote consensus system to which the message should be exported. + /// - `destination`: The location relative to the remote consensus system to which the message + /// should be sent on arrival. + /// - `xcm`: The message to be exported. + /// + /// As an example, to export a message for execution on Statemine (parachain #1000 in the + /// Kusama network), you would call with `network: NetworkId::Kusama` and + /// `destination: X1(Parachain(1000))`. Alternatively, to export a message for execution on + /// Polkadot, you would call with `network: NetworkId:: Polkadot` and `destination: Here`. + /// + /// Kind: *Instruction* + /// + /// Errors: *Fallible*. + ExportMessage { network: NetworkId, destination: InteriorMultiLocation, xcm: Xcm<()> }, + + /// Lock the locally held asset and prevent further transfer or withdrawal. + /// + /// This restriction may be removed by the `UnlockAsset` instruction being called with an + /// Origin of `unlocker` and a `target` equal to the current `Origin`. + /// + /// If the locking is successful, then a `NoteUnlockable` instruction is sent to `unlocker`. + /// + /// - `asset`: The asset(s) which should be locked. + /// - `unlocker`: The value which the Origin must be for a corresponding `UnlockAsset` + /// instruction to work. + /// + /// Kind: *Instruction*. + /// + /// Errors: + LockAsset { asset: MultiAsset, unlocker: MultiLocation }, + + /// Remove the lock over `asset` on this chain and (if nothing else is preventing it) allow the + /// asset to be transferred. + /// + /// - `asset`: The asset to be unlocked. + /// - `owner`: The owner of the asset on the local chain. + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction*. + /// + /// Errors: + UnlockAsset { asset: MultiAsset, target: MultiLocation }, + + /// Asset (`asset`) has been locked on the `origin` system and may not be transferred. It may + /// only be unlocked with the receipt of the `UnlockAsset` instruction from this chain. + /// + /// - `asset`: The asset(s) which are now unlockable from this origin. + /// - `owner`: The owner of the asset on the chain in which it was locked. This may be a + /// location specific to the origin network. + /// + /// Safety: `origin` must be trusted to have locked the corresponding `asset` + /// prior as a consequence of sending this message. + /// + /// Kind: *Trusted Indication*. + /// + /// Errors: + NoteUnlockable { asset: MultiAsset, owner: MultiLocation }, + + /// Send an `UnlockAsset` instruction to the `locker` for the given `asset`. + /// + /// This may fail if the local system is making use of the fact that the asset is locked or, + /// of course, if there is no record that the asset actually is locked. + /// + /// - `asset`: The asset(s) to be unlocked. + /// - `locker`: The location from which a previous `NoteUnlockable` was sent and to which + /// an `UnlockAsset` should be sent. + /// + /// Kind: *Instruction*. + /// + /// Errors: + RequestUnlock { asset: MultiAsset, locker: MultiLocation }, + + /// Sets the Fees Mode Register. + /// + /// - `jit_withdraw`: The fees mode item; if set to `true` then fees for any instructions + /// are withdrawn as needed using the same mechanism as `WithdrawAssets`. + /// + /// Kind: *Instruction*. + /// + /// Errors: + SetFeesMode { jit_withdraw: bool }, + + /// Set the Topic Register. + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction* + /// + /// Errors: + SetTopic([u8; 32]), + + /// Clear the Topic Register. + /// + /// Kind: *Instruction* + /// + /// Errors: None. + ClearTopic, + + /// Alter the current Origin to another given origin. + /// + /// Kind: *Instruction* + /// + /// Errors: If the existing state would not allow such a change. + AliasOrigin(MultiLocation), + + /// A directive to indicate that the origin expects free execution of the message. + /// + /// At execution time, this instruction just does a check on the Origin register. + /// However, at the barrier stage messages starting with this instruction can be disregarded if + /// the origin is not acceptable for free execution or the `weight_limit` is `Limited` and + /// insufficient. + /// + /// Kind: *Indication* + /// + /// Errors: If the given origin is `Some` and not equal to the current Origin register. + UnpaidExecution { weight_limit: WeightLimit, check_origin: Option }, +} + +impl Xcm { + pub fn into(self) -> Xcm { + Xcm::from(self) + } + pub fn from(xcm: Xcm) -> Self { + Self(xcm.0.into_iter().map(Instruction::::from).collect()) + } +} + +impl Instruction { + pub fn into(self) -> Instruction { + Instruction::from(self) + } + pub fn from(xcm: Instruction) -> Self { + use Instruction::*; + match xcm { + WithdrawAsset(assets) => WithdrawAsset(assets), + ReserveAssetDeposited(assets) => ReserveAssetDeposited(assets), + ReceiveTeleportedAsset(assets) => ReceiveTeleportedAsset(assets), + QueryResponse { query_id, response, max_weight, querier } => + QueryResponse { query_id, response, max_weight, querier }, + TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary }, + TransferReserveAsset { assets, dest, xcm } => + TransferReserveAsset { assets, dest, xcm }, + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, + HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, + HrmpChannelClosing { initiator, sender, recipient } => + HrmpChannelClosing { initiator, sender, recipient }, + Transact { origin_kind, require_weight_at_most, call } => + Transact { origin_kind, require_weight_at_most, call: call.into() }, + ReportError(response_info) => ReportError(response_info), + DepositAsset { assets, beneficiary } => DepositAsset { assets, beneficiary }, + DepositReserveAsset { assets, dest, xcm } => DepositReserveAsset { assets, dest, xcm }, + ExchangeAsset { give, want, maximal } => ExchangeAsset { give, want, maximal }, + InitiateReserveWithdraw { assets, reserve, xcm } => + InitiateReserveWithdraw { assets, reserve, xcm }, + InitiateTeleport { assets, dest, xcm } => InitiateTeleport { assets, dest, xcm }, + ReportHolding { response_info, assets } => ReportHolding { response_info, assets }, + BuyExecution { fees, weight_limit } => BuyExecution { fees, weight_limit }, + ClearOrigin => ClearOrigin, + DescendOrigin(who) => DescendOrigin(who), + RefundSurplus => RefundSurplus, + SetErrorHandler(xcm) => SetErrorHandler(xcm.into()), + SetAppendix(xcm) => SetAppendix(xcm.into()), + ClearError => ClearError, + ClaimAsset { assets, ticket } => ClaimAsset { assets, ticket }, + Trap(code) => Trap(code), + SubscribeVersion { query_id, max_response_weight } => + SubscribeVersion { query_id, max_response_weight }, + UnsubscribeVersion => UnsubscribeVersion, + BurnAsset(assets) => BurnAsset(assets), + ExpectAsset(assets) => ExpectAsset(assets), + ExpectOrigin(origin) => ExpectOrigin(origin), + ExpectError(error) => ExpectError(error), + QueryPallet { module_name, response_info } => + QueryPallet { module_name, response_info }, + ExpectPallet { index, name, module_name, crate_major, min_crate_minor } => + ExpectPallet { index, name, module_name, crate_major, min_crate_minor }, + ReportTransactStatus(response_info) => ReportTransactStatus(response_info), + ClearTransactStatus => ClearTransactStatus, + UniversalOrigin(j) => UniversalOrigin(j), + ExportMessage { network, destination, xcm } => + ExportMessage { network, destination, xcm }, + LockAsset { asset, unlocker } => LockAsset { asset, unlocker }, + UnlockAsset { asset, target } => UnlockAsset { asset, target }, + NoteUnlockable { asset, owner } => NoteUnlockable { asset, owner }, + RequestUnlock { asset, locker } => RequestUnlock { asset, locker }, + SetFeesMode { jit_withdraw } => SetFeesMode { jit_withdraw }, + SetTopic(topic) => SetTopic(topic), + ClearTopic => ClearTopic, + AliasOrigin(location) => AliasOrigin(location), + UnpaidExecution { weight_limit, check_origin } => + UnpaidExecution { weight_limit, check_origin }, + } + } +} + +// TODO: Automate Generation +impl> GetWeight for Instruction { + fn weight(&self) -> Weight { + use Instruction::*; + match self { + WithdrawAsset(assets) => W::withdraw_asset(assets), + ReserveAssetDeposited(assets) => W::reserve_asset_deposited(assets), + ReceiveTeleportedAsset(assets) => W::receive_teleported_asset(assets), + QueryResponse { query_id, response, max_weight, querier } => + W::query_response(query_id, response, max_weight, querier), + TransferAsset { assets, beneficiary } => W::transfer_asset(assets, beneficiary), + TransferReserveAsset { assets, dest, xcm } => + W::transfer_reserve_asset(&assets, dest, xcm), + Transact { origin_kind, require_weight_at_most, call } => + W::transact(origin_kind, require_weight_at_most, call), + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + W::hrmp_new_channel_open_request(sender, max_message_size, max_capacity), + HrmpChannelAccepted { recipient } => W::hrmp_channel_accepted(recipient), + HrmpChannelClosing { initiator, sender, recipient } => + W::hrmp_channel_closing(initiator, sender, recipient), + ClearOrigin => W::clear_origin(), + DescendOrigin(who) => W::descend_origin(who), + ReportError(response_info) => W::report_error(&response_info), + DepositAsset { assets, beneficiary } => W::deposit_asset(assets, beneficiary), + DepositReserveAsset { assets, dest, xcm } => + W::deposit_reserve_asset(assets, dest, xcm), + ExchangeAsset { give, want, maximal } => W::exchange_asset(give, want, maximal), + InitiateReserveWithdraw { assets, reserve, xcm } => + W::initiate_reserve_withdraw(assets, reserve, xcm), + InitiateTeleport { assets, dest, xcm } => W::initiate_teleport(assets, dest, xcm), + ReportHolding { response_info, assets } => W::report_holding(&response_info, &assets), + BuyExecution { fees, weight_limit } => W::buy_execution(fees, weight_limit), + RefundSurplus => W::refund_surplus(), + SetErrorHandler(xcm) => W::set_error_handler(xcm), + SetAppendix(xcm) => W::set_appendix(xcm), + ClearError => W::clear_error(), + ClaimAsset { assets, ticket } => W::claim_asset(assets, ticket), + Trap(code) => W::trap(code), + SubscribeVersion { query_id, max_response_weight } => + W::subscribe_version(query_id, max_response_weight), + UnsubscribeVersion => W::unsubscribe_version(), + BurnAsset(assets) => W::burn_asset(assets), + ExpectAsset(assets) => W::expect_asset(assets), + ExpectOrigin(origin) => W::expect_origin(origin), + ExpectError(error) => W::expect_error(error), + QueryPallet { module_name, response_info } => + W::query_pallet(module_name, response_info), + ExpectPallet { index, name, module_name, crate_major, min_crate_minor } => + W::expect_pallet(index, name, module_name, crate_major, min_crate_minor), + ReportTransactStatus(response_info) => W::report_transact_status(response_info), + ClearTransactStatus => W::clear_transact_status(), + UniversalOrigin(j) => W::universal_origin(j), + ExportMessage { network, destination, xcm } => + W::export_message(network, destination, xcm), + LockAsset { asset, unlocker } => W::lock_asset(asset, unlocker), + UnlockAsset { asset, target } => W::unlock_asset(asset, target), + NoteUnlockable { asset, owner } => W::note_unlockable(asset, owner), + RequestUnlock { asset, locker } => W::request_unlock(asset, locker), + SetFeesMode { jit_withdraw } => W::set_fees_mode(jit_withdraw), + SetTopic(topic) => W::set_topic(topic), + ClearTopic => W::clear_topic(), + AliasOrigin(location) => W::alias_origin(location), + UnpaidExecution { weight_limit, check_origin } => + W::unpaid_execution(weight_limit, check_origin), + } + } +} + +pub mod opaque { + /// The basic concrete type of `Xcm`, which doesn't make any assumptions about the + /// format of a call other than it is pre-encoded. + pub type Xcm = super::Xcm<()>; + + /// The basic concrete type of `Instruction`, which doesn't make any assumptions about the + /// format of a call other than it is pre-encoded. + pub type Instruction = super::Instruction<()>; +} + +// Convert from a v2 response to a v3 response. +impl TryFrom for Response { + type Error = (); + fn try_from(old_response: OldResponse) -> result::Result { + match old_response { + OldResponse::Assets(assets) => Ok(Self::Assets(assets.try_into()?)), + OldResponse::Version(version) => Ok(Self::Version(version)), + OldResponse::ExecutionResult(error) => Ok(Self::ExecutionResult(match error { + Some((i, e)) => Some((i, e.try_into()?)), + None => None, + })), + OldResponse::Null => Ok(Self::Null), + } + } +} + +// Convert from a v2 XCM to a v3 XCM. +impl TryFrom> for Xcm { + type Error = (); + fn try_from(old_xcm: OldXcm) -> result::Result { + Ok(Xcm(old_xcm.0.into_iter().map(TryInto::try_into).collect::>()?)) + } +} + +/// Default value for the proof size weight component. Set at 64 KB. +const DEFAULT_PROOF_SIZE: u64 = 64 * 1024; + +// Convert from a v2 instruction to a v3 instruction. +impl TryFrom> for Instruction { + type Error = (); + fn try_from(old_instruction: OldInstruction) -> result::Result { + use OldInstruction::*; + Ok(match old_instruction { + WithdrawAsset(assets) => Self::WithdrawAsset(assets.try_into()?), + ReserveAssetDeposited(assets) => Self::ReserveAssetDeposited(assets.try_into()?), + ReceiveTeleportedAsset(assets) => Self::ReceiveTeleportedAsset(assets.try_into()?), + QueryResponse { query_id, response, max_weight } => Self::QueryResponse { + query_id, + response: response.try_into()?, + max_weight: Weight::from_parts(max_weight, DEFAULT_PROOF_SIZE), + querier: None, + }, + TransferAsset { assets, beneficiary } => Self::TransferAsset { + assets: assets.try_into()?, + beneficiary: beneficiary.try_into()?, + }, + TransferReserveAsset { assets, dest, xcm } => Self::TransferReserveAsset { + assets: assets.try_into()?, + dest: dest.try_into()?, + xcm: xcm.try_into()?, + }, + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + Self::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, + HrmpChannelAccepted { recipient } => Self::HrmpChannelAccepted { recipient }, + HrmpChannelClosing { initiator, sender, recipient } => + Self::HrmpChannelClosing { initiator, sender, recipient }, + Transact { origin_type, require_weight_at_most, call } => Self::Transact { + origin_kind: origin_type, + require_weight_at_most: Weight::from_parts( + require_weight_at_most, + DEFAULT_PROOF_SIZE, + ), + call: call.into(), + }, + ReportError { query_id, dest, max_response_weight } => { + let response_info = QueryResponseInfo { + destination: dest.try_into()?, + query_id, + max_weight: Weight::from_parts(max_response_weight, DEFAULT_PROOF_SIZE), + }; + Self::ReportError(response_info) + }, + DepositAsset { assets, max_assets, beneficiary } => Self::DepositAsset { + assets: (assets, max_assets).try_into()?, + beneficiary: beneficiary.try_into()?, + }, + DepositReserveAsset { assets, max_assets, dest, xcm } => { + let assets = (assets, max_assets).try_into()?; + Self::DepositReserveAsset { assets, dest: dest.try_into()?, xcm: xcm.try_into()? } + }, + ExchangeAsset { give, receive } => { + let give = give.try_into()?; + let want = receive.try_into()?; + Self::ExchangeAsset { give, want, maximal: true } + }, + InitiateReserveWithdraw { assets, reserve, xcm } => Self::InitiateReserveWithdraw { + assets: assets.try_into()?, + reserve: reserve.try_into()?, + xcm: xcm.try_into()?, + }, + InitiateTeleport { assets, dest, xcm } => Self::InitiateTeleport { + assets: assets.try_into()?, + dest: dest.try_into()?, + xcm: xcm.try_into()?, + }, + QueryHolding { query_id, dest, assets, max_response_weight } => { + let response_info = QueryResponseInfo { + destination: dest.try_into()?, + query_id, + max_weight: Weight::from_parts(max_response_weight, DEFAULT_PROOF_SIZE), + }; + Self::ReportHolding { response_info, assets: assets.try_into()? } + }, + BuyExecution { fees, weight_limit } => Self::BuyExecution { + fees: fees.try_into()?, + weight_limit: weight_limit.try_into()?, + }, + ClearOrigin => Self::ClearOrigin, + DescendOrigin(who) => Self::DescendOrigin(who.try_into()?), + RefundSurplus => Self::RefundSurplus, + SetErrorHandler(xcm) => Self::SetErrorHandler(xcm.try_into()?), + SetAppendix(xcm) => Self::SetAppendix(xcm.try_into()?), + ClearError => Self::ClearError, + ClaimAsset { assets, ticket } => { + let assets = assets.try_into()?; + let ticket = ticket.try_into()?; + Self::ClaimAsset { assets, ticket } + }, + Trap(code) => Self::Trap(code), + SubscribeVersion { query_id, max_response_weight } => Self::SubscribeVersion { + query_id, + max_response_weight: Weight::from_parts(max_response_weight, DEFAULT_PROOF_SIZE), + }, + UnsubscribeVersion => Self::UnsubscribeVersion, + }) + } +} + +#[cfg(test)] +mod tests { + use super::{prelude::*, *}; + use crate::v2::{ + Junctions::Here as OldHere, MultiAssetFilter as OldMultiAssetFilter, + WildMultiAsset as OldWildMultiAsset, + }; + + #[test] + fn basic_roundtrip_works() { + let xcm = Xcm::<()>(vec![TransferAsset { + assets: (Here, 1u128).into(), + beneficiary: Here.into(), + }]); + let old_xcm = OldXcm::<()>(vec![OldInstruction::TransferAsset { + assets: (OldHere, 1).into(), + beneficiary: OldHere.into(), + }]); + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + assert_eq!(new_xcm, xcm); + } + + #[test] + fn teleport_roundtrip_works() { + let xcm = Xcm::<()>(vec![ + ReceiveTeleportedAsset((Here, 1u128).into()), + ClearOrigin, + DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Here.into() }, + ]); + let old_xcm: OldXcm<()> = OldXcm::<()>(vec![ + OldInstruction::ReceiveTeleportedAsset((OldHere, 1).into()), + OldInstruction::ClearOrigin, + OldInstruction::DepositAsset { + assets: crate::v2::MultiAssetFilter::Wild(crate::v2::WildMultiAsset::All), + max_assets: 1, + beneficiary: OldHere.into(), + }, + ]); + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + assert_eq!(new_xcm, xcm); + } + + #[test] + fn reserve_deposit_roundtrip_works() { + let xcm = Xcm::<()>(vec![ + ReserveAssetDeposited((Here, 1u128).into()), + ClearOrigin, + BuyExecution { + fees: (Here, 1u128).into(), + weight_limit: Some(Weight::from_parts(1, DEFAULT_PROOF_SIZE)).into(), + }, + DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Here.into() }, + ]); + let old_xcm = OldXcm::<()>(vec![ + OldInstruction::ReserveAssetDeposited((OldHere, 1).into()), + OldInstruction::ClearOrigin, + OldInstruction::BuyExecution { + fees: (OldHere, 1).into(), + weight_limit: Some(1).into(), + }, + OldInstruction::DepositAsset { + assets: crate::v2::MultiAssetFilter::Wild(crate::v2::WildMultiAsset::All), + max_assets: 1, + beneficiary: OldHere.into(), + }, + ]); + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + assert_eq!(new_xcm, xcm); + } + + #[test] + fn deposit_asset_roundtrip_works() { + let xcm = Xcm::<()>(vec![ + WithdrawAsset((Here, 1u128).into()), + DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Here.into() }, + ]); + let old_xcm = OldXcm::<()>(vec![ + OldInstruction::WithdrawAsset((OldHere, 1).into()), + OldInstruction::DepositAsset { + assets: OldMultiAssetFilter::Wild(OldWildMultiAsset::All), + max_assets: 1, + beneficiary: OldHere.into(), + }, + ]); + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + assert_eq!(new_xcm, xcm); + } + + #[test] + fn deposit_reserve_asset_roundtrip_works() { + let xcm = Xcm::<()>(vec![ + WithdrawAsset((Here, 1u128).into()), + DepositReserveAsset { + assets: Wild(AllCounted(1)), + dest: Here.into(), + xcm: Xcm::<()>(vec![]), + }, + ]); + let old_xcm = OldXcm::<()>(vec![ + OldInstruction::WithdrawAsset((OldHere, 1).into()), + OldInstruction::DepositReserveAsset { + assets: OldMultiAssetFilter::Wild(OldWildMultiAsset::All), + max_assets: 1, + dest: OldHere.into(), + xcm: OldXcm::<()>(vec![]), + }, + ]); + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + assert_eq!(new_xcm, xcm); + } +} diff --git a/xcm/src/v3/multiasset.rs b/xcm/src/v3/multiasset.rs new file mode 100644 index 000000000000..0dd6b55826c8 --- /dev/null +++ b/xcm/src/v3/multiasset.rs @@ -0,0 +1,951 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Substrate 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. + +// Substrate 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 Polkadot. If not, see . + +//! Cross-Consensus Message format asset data structures. +//! +//! This encompasses four types for representing assets: +//! - `MultiAsset`: A description of a single asset, either an instance of a non-fungible or some amount of a fungible. +//! - `MultiAssets`: A collection of `MultiAsset`s. These are stored in a `Vec` and sorted with fungibles first. +//! - `Wild`: A single asset wildcard, this can either be "all" assets, or all assets of a specific kind. +//! - `MultiAssetFilter`: A combination of `Wild` and `MultiAssets` designed for efficiently filtering an XCM holding +//! account. + +use super::{InteriorMultiLocation, MultiLocation}; +use crate::v2::{ + AssetId as OldAssetId, AssetInstance as OldAssetInstance, Fungibility as OldFungibility, + MultiAsset as OldMultiAsset, MultiAssetFilter as OldMultiAssetFilter, + MultiAssets as OldMultiAssets, WildFungibility as OldWildFungibility, + WildMultiAsset as OldWildMultiAsset, +}; +use alloc::{vec, vec::Vec}; +use core::{ + cmp::Ordering, + convert::{TryFrom, TryInto}, +}; +use parity_scale_codec::{self as codec, Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +/// A general identifier for an instance of a non-fungible asset class. +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen, +)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum AssetInstance { + /// Undefined - used if the non-fungible asset class has only one instance. + Undefined, + + /// A compact index. Technically this could be greater than `u128`, but this implementation supports only + /// values up to `2**128 - 1`. + Index(#[codec(compact)] u128), + + /// A 4-byte fixed-length datum. + Array4([u8; 4]), + + /// An 8-byte fixed-length datum. + Array8([u8; 8]), + + /// A 16-byte fixed-length datum. + Array16([u8; 16]), + + /// A 32-byte fixed-length datum. + Array32([u8; 32]), +} + +impl TryFrom for AssetInstance { + type Error = (); + fn try_from(value: OldAssetInstance) -> Result { + use OldAssetInstance::*; + Ok(match value { + Undefined => Self::Undefined, + Index(n) => Self::Index(n), + Array4(n) => Self::Array4(n), + Array8(n) => Self::Array8(n), + Array16(n) => Self::Array16(n), + Array32(n) => Self::Array32(n), + Blob(_) => return Err(()), + }) + } +} + +impl From<()> for AssetInstance { + fn from(_: ()) -> Self { + Self::Undefined + } +} + +impl From<[u8; 4]> for AssetInstance { + fn from(x: [u8; 4]) -> Self { + Self::Array4(x) + } +} + +impl From<[u8; 8]> for AssetInstance { + fn from(x: [u8; 8]) -> Self { + Self::Array8(x) + } +} + +impl From<[u8; 16]> for AssetInstance { + fn from(x: [u8; 16]) -> Self { + Self::Array16(x) + } +} + +impl From<[u8; 32]> for AssetInstance { + fn from(x: [u8; 32]) -> Self { + Self::Array32(x) + } +} + +impl From for AssetInstance { + fn from(x: u8) -> Self { + Self::Index(x as u128) + } +} + +impl From for AssetInstance { + fn from(x: u16) -> Self { + Self::Index(x as u128) + } +} + +impl From for AssetInstance { + fn from(x: u32) -> Self { + Self::Index(x as u128) + } +} + +impl From for AssetInstance { + fn from(x: u64) -> Self { + Self::Index(x as u128) + } +} + +impl TryFrom for () { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Undefined => Ok(()), + _ => Err(()), + } + } +} + +impl TryFrom for [u8; 4] { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Array4(x) => Ok(x), + _ => Err(()), + } + } +} + +impl TryFrom for [u8; 8] { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Array8(x) => Ok(x), + _ => Err(()), + } + } +} + +impl TryFrom for [u8; 16] { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Array16(x) => Ok(x), + _ => Err(()), + } + } +} + +impl TryFrom for [u8; 32] { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Array32(x) => Ok(x), + _ => Err(()), + } + } +} + +impl TryFrom for u8 { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Index(x) => x.try_into().map_err(|_| ()), + _ => Err(()), + } + } +} + +impl TryFrom for u16 { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Index(x) => x.try_into().map_err(|_| ()), + _ => Err(()), + } + } +} + +impl TryFrom for u32 { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Index(x) => x.try_into().map_err(|_| ()), + _ => Err(()), + } + } +} + +impl TryFrom for u64 { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Index(x) => x.try_into().map_err(|_| ()), + _ => Err(()), + } + } +} + +impl TryFrom for u128 { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Index(x) => Ok(x), + _ => Err(()), + } + } +} + +/// Classification of whether an asset is fungible or not, along with a mandatory amount or instance. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum Fungibility { + /// A fungible asset; we record a number of units, as a `u128` in the inner item. + Fungible(#[codec(compact)] u128), + /// A non-fungible asset. We record the instance identifier in the inner item. Only one asset + /// of each instance identifier may ever be in existence at once. + NonFungible(AssetInstance), +} + +impl Fungibility { + pub fn is_kind(&self, w: WildFungibility) -> bool { + use Fungibility::*; + use WildFungibility::{Fungible as WildFungible, NonFungible as WildNonFungible}; + matches!((self, w), (Fungible(_), WildFungible) | (NonFungible(_), WildNonFungible)) + } +} + +impl From for Fungibility { + fn from(amount: i32) -> Fungibility { + debug_assert_ne!(amount, 0); + Fungibility::Fungible(amount as u128) + } +} + +impl From for Fungibility { + fn from(amount: u128) -> Fungibility { + debug_assert_ne!(amount, 0); + Fungibility::Fungible(amount) + } +} + +impl> From for Fungibility { + fn from(instance: T) -> Fungibility { + Fungibility::NonFungible(instance.into()) + } +} + +impl TryFrom for Fungibility { + type Error = (); + fn try_from(value: OldFungibility) -> Result { + use OldFungibility::*; + Ok(match value { + Fungible(n) => Self::Fungible(n), + NonFungible(i) => Self::NonFungible(i.try_into()?), + }) + } +} + +/// Classification of whether an asset is fungible or not. +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, +)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum WildFungibility { + /// The asset is fungible. + Fungible, + /// The asset is not fungible. + NonFungible, +} + +impl TryFrom for WildFungibility { + type Error = (); + fn try_from(value: OldWildFungibility) -> Result { + use OldWildFungibility::*; + Ok(match value { + Fungible => Self::Fungible, + NonFungible => Self::NonFungible, + }) + } +} + +/// Classification of an asset being concrete or abstract. +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, +)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum AssetId { + /// A specific location identifying an asset. + Concrete(MultiLocation), + /// An abstract location; this is a name which may mean different specific locations on + /// different chains at different times. + Abstract([u8; 32]), +} + +impl> From for AssetId { + fn from(x: T) -> Self { + Self::Concrete(x.into()) + } +} + +impl From<[u8; 32]> for AssetId { + fn from(x: [u8; 32]) -> Self { + Self::Abstract(x) + } +} + +impl TryFrom for AssetId { + type Error = (); + fn try_from(old: OldAssetId) -> Result { + use OldAssetId::*; + Ok(match old { + Concrete(l) => Self::Concrete(l.try_into()?), + Abstract(v) if v.len() <= 32 => { + let mut r = [0u8; 32]; + r[..v.len()].copy_from_slice(&v[..]); + Self::Abstract(r) + }, + _ => return Err(()), + }) + } +} + +impl AssetId { + /// Prepend a `MultiLocation` to a concrete asset, giving it a new root location. + pub fn prepend_with(&mut self, prepend: &MultiLocation) -> Result<(), ()> { + if let AssetId::Concrete(ref mut l) = self { + l.prepend_with(*prepend).map_err(|_| ())?; + } + Ok(()) + } + + /// Mutate the asset to represent the same value from the perspective of a new `target` + /// location. The local chain's location is provided in `context`. + pub fn reanchor( + &mut self, + target: &MultiLocation, + context: InteriorMultiLocation, + ) -> Result<(), ()> { + if let AssetId::Concrete(ref mut l) = self { + l.reanchor(target, context)?; + } + Ok(()) + } + + /// Use the value of `self` along with a `fun` fungibility specifier to create the corresponding `MultiAsset` value. + pub fn into_multiasset(self, fun: Fungibility) -> MultiAsset { + MultiAsset { fun, id: self } + } + + /// Use the value of `self` along with a `fun` fungibility specifier to create the corresponding `WildMultiAsset` + /// wildcard (`AllOf`) value. + pub fn into_wild(self, fun: WildFungibility) -> WildMultiAsset { + WildMultiAsset::AllOf { fun, id: self } + } +} + +/// Either an amount of a single fungible asset, or a single well-identified non-fungible asset. +#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct MultiAsset { + /// The overall asset identity (aka *class*, in the case of a non-fungible). + pub id: AssetId, + /// The fungibility of the asset, which contains either the amount (in the case of a fungible + /// asset) or the *insance ID`, the secondary asset identifier. + pub fun: Fungibility, +} + +impl PartialOrd for MultiAsset { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for MultiAsset { + fn cmp(&self, other: &Self) -> Ordering { + match (&self.fun, &other.fun) { + (Fungibility::Fungible(..), Fungibility::NonFungible(..)) => Ordering::Less, + (Fungibility::NonFungible(..), Fungibility::Fungible(..)) => Ordering::Greater, + _ => (&self.id, &self.fun).cmp(&(&other.id, &other.fun)), + } + } +} + +impl, B: Into> From<(A, B)> for MultiAsset { + fn from((id, fun): (A, B)) -> MultiAsset { + MultiAsset { fun: fun.into(), id: id.into() } + } +} + +impl MultiAsset { + pub fn is_fungible(&self, maybe_id: Option) -> bool { + use Fungibility::*; + matches!(self.fun, Fungible(..)) && maybe_id.map_or(true, |i| i == self.id) + } + + pub fn is_non_fungible(&self, maybe_id: Option) -> bool { + use Fungibility::*; + matches!(self.fun, NonFungible(..)) && maybe_id.map_or(true, |i| i == self.id) + } + + /// Prepend a `MultiLocation` to a concrete asset, giving it a new root location. + pub fn prepend_with(&mut self, prepend: &MultiLocation) -> Result<(), ()> { + self.id.prepend_with(prepend) + } + + /// Mutate the location of the asset identifier if concrete, giving it the same location + /// relative to a `target` context. The local context is provided as `context`. + pub fn reanchor( + &mut self, + target: &MultiLocation, + context: InteriorMultiLocation, + ) -> Result<(), ()> { + self.id.reanchor(target, context) + } + + /// Mutate the location of the asset identifier if concrete, giving it the same location + /// relative to a `target` context. The local context is provided as `context`. + pub fn reanchored( + mut self, + target: &MultiLocation, + context: InteriorMultiLocation, + ) -> Result { + self.id.reanchor(target, context)?; + Ok(self) + } + + /// Returns true if `self` is a super-set of the given `inner` asset. + pub fn contains(&self, inner: &MultiAsset) -> bool { + use Fungibility::*; + if self.id == inner.id { + match (&self.fun, &inner.fun) { + (Fungible(a), Fungible(i)) if a >= i => return true, + (NonFungible(a), NonFungible(i)) if a == i => return true, + _ => (), + } + } + false + } +} + +impl TryFrom for MultiAsset { + type Error = (); + fn try_from(old: OldMultiAsset) -> Result { + Ok(Self { id: old.id.try_into()?, fun: old.fun.try_into()? }) + } +} + +/// A `Vec` of `MultiAsset`s. +/// +/// There are a number of invariants which the construction and mutation functions must ensure are +/// maintained: +/// - It may contain no items of duplicate asset class; +/// - All items must be ordered; +/// - The number of items should grow no larger than `MAX_ITEMS_IN_MULTIASSETS`. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, TypeInfo, Default)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct MultiAssets(Vec); + +/// Maximum number of items we expect in a single `MultiAssets` value. Note this is not (yet) +/// enforced, and just serves to provide a sensible `max_encoded_len` for `MultiAssets`. +const MAX_ITEMS_IN_MULTIASSETS: usize = 20; + +impl MaxEncodedLen for MultiAssets { + fn max_encoded_len() -> usize { + MultiAsset::max_encoded_len() * MAX_ITEMS_IN_MULTIASSETS + } +} + +impl Decode for MultiAssets { + fn decode(input: &mut I) -> Result { + Self::from_sorted_and_deduplicated(Vec::::decode(input)?) + .map_err(|()| "Out of order".into()) + } +} + +impl TryFrom for MultiAssets { + type Error = (); + fn try_from(old: OldMultiAssets) -> Result { + let v = old + .drain() + .into_iter() + .map(MultiAsset::try_from) + .collect::, ()>>()?; + Ok(MultiAssets(v)) + } +} + +impl From> for MultiAssets { + fn from(mut assets: Vec) -> Self { + let mut res = Vec::with_capacity(assets.len()); + if !assets.is_empty() { + assets.sort(); + let mut iter = assets.into_iter(); + if let Some(first) = iter.next() { + let last = iter.fold(first, |a, b| -> MultiAsset { + match (a, b) { + ( + MultiAsset { fun: Fungibility::Fungible(a_amount), id: a_id }, + MultiAsset { fun: Fungibility::Fungible(b_amount), id: b_id }, + ) if a_id == b_id => MultiAsset { + id: a_id, + fun: Fungibility::Fungible(a_amount.saturating_add(b_amount)), + }, + ( + MultiAsset { fun: Fungibility::NonFungible(a_instance), id: a_id }, + MultiAsset { fun: Fungibility::NonFungible(b_instance), id: b_id }, + ) if a_id == b_id && a_instance == b_instance => + MultiAsset { fun: Fungibility::NonFungible(a_instance), id: a_id }, + (to_push, to_remember) => { + res.push(to_push); + to_remember + }, + } + }); + res.push(last); + } + } + Self(res) + } +} + +impl> From for MultiAssets { + fn from(x: T) -> Self { + Self(vec![x.into()]) + } +} + +impl MultiAssets { + /// A new (empty) value. + pub fn new() -> Self { + Self(Vec::new()) + } + + /// Create a new instance of `MultiAssets` from a `Vec` whose contents are sorted and + /// which contain no duplicates. + /// + /// Returns `Ok` if the operation succeeds and `Err` if `r` is out of order or had duplicates. If you can't + /// guarantee that `r` is sorted and deduplicated, then use `From::>::from` which is infallible. + pub fn from_sorted_and_deduplicated(r: Vec) -> Result { + if r.is_empty() { + return Ok(Self(Vec::new())) + } + r.iter().skip(1).try_fold(&r[0], |a, b| -> Result<&MultiAsset, ()> { + if a.id < b.id || a < b && (a.is_non_fungible(None) || b.is_non_fungible(None)) { + Ok(b) + } else { + Err(()) + } + })?; + Ok(Self(r)) + } + + /// Create a new instance of `MultiAssets` from a `Vec` whose contents are sorted and + /// which contain no duplicates. + /// + /// In release mode, this skips any checks to ensure that `r` is correct, making it a negligible-cost operation. + /// Generally though you should avoid using it unless you have a strict proof that `r` is valid. + #[cfg(test)] + pub fn from_sorted_and_deduplicated_skip_checks(r: Vec) -> Self { + Self::from_sorted_and_deduplicated(r).expect("Invalid input r is not sorted/deduped") + } + /// Create a new instance of `MultiAssets` from a `Vec` whose contents are sorted and + /// which contain no duplicates. + /// + /// In release mode, this skips any checks to ensure that `r` is correct, making it a negligible-cost operation. + /// Generally though you should avoid using it unless you have a strict proof that `r` is valid. + /// + /// In test mode, this checks anyway and panics on fail. + #[cfg(not(test))] + pub fn from_sorted_and_deduplicated_skip_checks(r: Vec) -> Self { + Self(r) + } + + /// Add some asset onto the list, saturating. This is quite a laborious operation since it maintains the ordering. + pub fn push(&mut self, a: MultiAsset) { + for asset in self.0.iter_mut().filter(|x| x.id == a.id) { + match (&a.fun, &mut asset.fun) { + (Fungibility::Fungible(amount), Fungibility::Fungible(balance)) => { + *balance = balance.saturating_add(*amount); + return + }, + (Fungibility::NonFungible(inst1), Fungibility::NonFungible(inst2)) + if inst1 == inst2 => + return, + _ => (), + } + } + self.0.push(a); + self.0.sort(); + } + + /// Returns `true` if this definitely represents no asset. + pub fn is_none(&self) -> bool { + self.0.is_empty() + } + + /// Returns true if `self` is a super-set of the given `inner` asset. + pub fn contains(&self, inner: &MultiAsset) -> bool { + self.0.iter().any(|i| i.contains(inner)) + } + + /// Consume `self` and return the inner vec. + #[deprecated = "Use `into_inner()` instead"] + pub fn drain(self) -> Vec { + self.0 + } + + /// Consume `self` and return the inner vec. + pub fn into_inner(self) -> Vec { + self.0 + } + + /// Return a reference to the inner vec. + pub fn inner(&self) -> &Vec { + &self.0 + } + + /// Return the number of distinct asset instances contained. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Prepend a `MultiLocation` to any concrete asset items, giving it a new root location. + pub fn prepend_with(&mut self, prefix: &MultiLocation) -> Result<(), ()> { + self.0.iter_mut().try_for_each(|i| i.prepend_with(prefix)) + } + + /// Mutate the location of the asset identifier if concrete, giving it the same location + /// relative to a `target` context. The local context is provided as `context`. + pub fn reanchor( + &mut self, + target: &MultiLocation, + context: InteriorMultiLocation, + ) -> Result<(), ()> { + self.0.iter_mut().try_for_each(|i| i.reanchor(target, context)) + } + + /// Return a reference to an item at a specific index or `None` if it doesn't exist. + pub fn get(&self, index: usize) -> Option<&MultiAsset> { + self.0.get(index) + } +} + +/// A wildcard representing a set of assets. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum WildMultiAsset { + /// All assets in Holding. + All, + /// All assets in Holding of a given fungibility and ID. + AllOf { id: AssetId, fun: WildFungibility }, + /// All assets in Holding, up to `u32` individual assets (different instances of non-fungibles + /// are separate assets). + AllCounted(#[codec(compact)] u32), + /// All assets in Holding of a given fungibility and ID up to `count` individual assets + /// (different instances of non-fungibles are separate assets). + AllOfCounted { + id: AssetId, + fun: WildFungibility, + #[codec(compact)] + count: u32, + }, +} + +impl TryFrom for WildMultiAsset { + type Error = (); + fn try_from(old: OldWildMultiAsset) -> Result { + use OldWildMultiAsset::*; + Ok(match old { + AllOf { id, fun } => Self::AllOf { id: id.try_into()?, fun: fun.try_into()? }, + All => Self::All, + }) + } +} + +impl TryFrom<(OldWildMultiAsset, u32)> for WildMultiAsset { + type Error = (); + fn try_from(old: (OldWildMultiAsset, u32)) -> Result { + use OldWildMultiAsset::*; + let count = old.1; + Ok(match old.0 { + AllOf { id, fun } => + Self::AllOfCounted { id: id.try_into()?, fun: fun.try_into()?, count }, + All => Self::AllCounted(count), + }) + } +} + +impl WildMultiAsset { + /// Returns true if `self` is a super-set of the given `inner` asset. + pub fn contains(&self, inner: &MultiAsset) -> bool { + use WildMultiAsset::*; + match self { + AllOfCounted { count: 0, .. } | AllCounted(0) => false, + AllOf { fun, id } | AllOfCounted { id, fun, .. } => + inner.fun.is_kind(*fun) && &inner.id == id, + All | AllCounted(_) => true, + } + } + + /// Returns true if the wild element of `self` matches `inner`. + /// + /// Note that for `Counted` variants of wildcards, then it will disregard the count except for + /// always returning `false` when equal to 0. + #[deprecated = "Use `contains` instead"] + pub fn matches(&self, inner: &MultiAsset) -> bool { + self.contains(inner) + } + + /// Mutate the asset to represent the same value from the perspective of a new `target` + /// location. The local chain's location is provided in `context`. + pub fn reanchor( + &mut self, + target: &MultiLocation, + context: InteriorMultiLocation, + ) -> Result<(), ()> { + use WildMultiAsset::*; + match self { + AllOf { ref mut id, .. } | AllOfCounted { ref mut id, .. } => + id.reanchor(target, context), + All | AllCounted(_) => Ok(()), + } + } + + /// Maximum count of assets allowed to match, if any. + pub fn count(&self) -> Option { + use WildMultiAsset::*; + match self { + AllOfCounted { count, .. } | AllCounted(count) => Some(*count), + All | AllOf { .. } => None, + } + } + + /// Explicit limit on number of assets allowed to match, if any. + pub fn limit(&self) -> Option { + self.count() + } + + /// Consume self and return the equivalent version but counted and with the `count` set to the + /// given parameter. + pub fn counted(self, count: u32) -> Self { + use WildMultiAsset::*; + match self { + AllOfCounted { fun, id, .. } | AllOf { fun, id } => AllOfCounted { fun, id, count }, + All | AllCounted(_) => AllCounted(count), + } + } +} + +impl, B: Into> From<(A, B)> for WildMultiAsset { + fn from((id, fun): (A, B)) -> WildMultiAsset { + WildMultiAsset::AllOf { fun: fun.into(), id: id.into() } + } +} + +/// `MultiAsset` collection, defined either by a number of `MultiAssets` or a single wildcard. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum MultiAssetFilter { + /// Specify the filter as being everything contained by the given `MultiAssets` inner. + Definite(MultiAssets), + /// Specify the filter as the given `WildMultiAsset` wildcard. + Wild(WildMultiAsset), +} + +impl> From for MultiAssetFilter { + fn from(x: T) -> Self { + Self::Wild(x.into()) + } +} + +impl From for MultiAssetFilter { + fn from(x: MultiAsset) -> Self { + Self::Definite(vec![x].into()) + } +} + +impl From> for MultiAssetFilter { + fn from(x: Vec) -> Self { + Self::Definite(x.into()) + } +} + +impl From for MultiAssetFilter { + fn from(x: MultiAssets) -> Self { + Self::Definite(x) + } +} + +impl MultiAssetFilter { + /// Returns true if `inner` would be matched by `self`. + /// + /// Note that for `Counted` variants of wildcards, then it will disregard the count except for + /// always returning `false` when equal to 0. + pub fn matches(&self, inner: &MultiAsset) -> bool { + match self { + MultiAssetFilter::Definite(ref assets) => assets.contains(inner), + MultiAssetFilter::Wild(ref wild) => wild.contains(inner), + } + } + + /// Mutate the location of the asset identifier if concrete, giving it the same location + /// relative to a `target` context. The local context is provided as `context`. + pub fn reanchor( + &mut self, + target: &MultiLocation, + context: InteriorMultiLocation, + ) -> Result<(), ()> { + match self { + MultiAssetFilter::Definite(ref mut assets) => assets.reanchor(target, context), + MultiAssetFilter::Wild(ref mut wild) => wild.reanchor(target, context), + } + } + + /// Maximum count of assets it is possible to match, if known. + pub fn count(&self) -> Option { + use MultiAssetFilter::*; + match self { + Definite(x) => Some(x.len() as u32), + Wild(x) => x.count(), + } + } + + /// Explicit limit placed on the number of items, if any. + pub fn limit(&self) -> Option { + use MultiAssetFilter::*; + match self { + Definite(_) => None, + Wild(x) => x.limit(), + } + } +} + +impl TryFrom for MultiAssetFilter { + type Error = (); + fn try_from(old: OldMultiAssetFilter) -> Result { + Ok(match old { + OldMultiAssetFilter::Definite(x) => Self::Definite(x.try_into()?), + OldMultiAssetFilter::Wild(x) => Self::Wild(x.try_into()?), + }) + } +} + +impl TryFrom<(OldMultiAssetFilter, u32)> for MultiAssetFilter { + type Error = (); + fn try_from(old: (OldMultiAssetFilter, u32)) -> Result { + let count = old.1; + Ok(match old.0 { + OldMultiAssetFilter::Definite(x) if count >= x.len() as u32 => + Self::Definite(x.try_into()?), + OldMultiAssetFilter::Wild(x) => Self::Wild((x, count).try_into()?), + _ => return Err(()), + }) + } +} + +#[cfg(test)] +mod tests { + use super::super::prelude::*; + + #[test] + fn conversion_works() { + let _: MultiAssets = (Here, 1u128).into(); + } + + #[test] + fn from_sorted_and_deduplicated_works() { + use super::*; + use alloc::vec; + + let empty = vec![]; + let r = MultiAssets::from_sorted_and_deduplicated(empty); + assert_eq!(r, Ok(MultiAssets(vec![]))); + + let dup_fun = vec![(Here, 100).into(), (Here, 10).into()]; + let r = MultiAssets::from_sorted_and_deduplicated(dup_fun); + assert!(r.is_err()); + + let dup_nft = vec![(Here, *b"notgood!").into(), (Here, *b"notgood!").into()]; + let r = MultiAssets::from_sorted_and_deduplicated(dup_nft); + assert!(r.is_err()); + + let good_fun = vec![(Here, 10).into(), (Parent, 10).into()]; + let r = MultiAssets::from_sorted_and_deduplicated(good_fun.clone()); + assert_eq!(r, Ok(MultiAssets(good_fun))); + + let bad_fun = vec![(Parent, 10).into(), (Here, 10).into()]; + let r = MultiAssets::from_sorted_and_deduplicated(bad_fun); + assert!(r.is_err()); + + let good_abstract_fun = vec![(Here, 100).into(), ([0u8; 32], 10).into()]; + let r = MultiAssets::from_sorted_and_deduplicated(good_abstract_fun.clone()); + assert_eq!(r, Ok(MultiAssets(good_abstract_fun))); + + let bad_abstract_fun = vec![([0u8; 32], 10).into(), (Here, 10).into()]; + let r = MultiAssets::from_sorted_and_deduplicated(bad_abstract_fun); + assert!(r.is_err()); + + let good_nft = vec![(Here, ()).into(), (Here, *b"good").into()]; + let r = MultiAssets::from_sorted_and_deduplicated(good_nft.clone()); + assert_eq!(r, Ok(MultiAssets(good_nft))); + + let bad_nft = vec![(Here, *b"bad!").into(), (Here, ()).into()]; + let r = MultiAssets::from_sorted_and_deduplicated(bad_nft); + assert!(r.is_err()); + + let good_abstract_nft = vec![(Here, ()).into(), ([0u8; 32], ()).into()]; + let r = MultiAssets::from_sorted_and_deduplicated(good_abstract_nft.clone()); + assert_eq!(r, Ok(MultiAssets(good_abstract_nft))); + + let bad_abstract_nft = vec![([0u8; 32], ()).into(), (Here, ()).into()]; + let r = MultiAssets::from_sorted_and_deduplicated(bad_abstract_nft); + assert!(r.is_err()); + + let mixed_good = vec![(Here, 10).into(), (Here, *b"good").into()]; + let r = MultiAssets::from_sorted_and_deduplicated(mixed_good.clone()); + assert_eq!(r, Ok(MultiAssets(mixed_good))); + + let mixed_bad = vec![(Here, *b"bad!").into(), (Here, 10).into()]; + let r = MultiAssets::from_sorted_and_deduplicated(mixed_bad); + assert!(r.is_err()); + } +} diff --git a/xcm/src/v3/multilocation.rs b/xcm/src/v3/multilocation.rs new file mode 100644 index 000000000000..698276627afa --- /dev/null +++ b/xcm/src/v3/multilocation.rs @@ -0,0 +1,710 @@ +// Copyright 2020-2021 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! XCM `MultiLocation` datatype. + +use super::{Junction, Junctions}; +use crate::{v2::MultiLocation as OldMultiLocation, VersionedMultiLocation}; +use core::{ + convert::{TryFrom, TryInto}, + result, +}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +/// A relative path between state-bearing consensus systems. +/// +/// A location in a consensus system is defined as an *isolatable state machine* held within global +/// consensus. The location in question need not have a sophisticated consensus algorithm of its +/// own; a single account within Ethereum, for example, could be considered a location. +/// +/// A very-much non-exhaustive list of types of location include: +/// - A (normal, layer-1) block chain, e.g. the Bitcoin mainnet or a parachain. +/// - A layer-0 super-chain, e.g. the Polkadot Relay chain. +/// - A layer-2 smart contract, e.g. an ERC-20 on Ethereum. +/// - A logical functional component of a chain, e.g. a single instance of a pallet on a Frame-based +/// Substrate chain. +/// - An account. +/// +/// A `MultiLocation` is a *relative identifier*, meaning that it can only be used to define the +/// relative path between two locations, and cannot generally be used to refer to a location +/// universally. It is comprised of an integer number of parents specifying the number of times to +/// "escape" upwards into the containing consensus system and then a number of *junctions*, each +/// diving down and specifying some interior portion of state (which may be considered a +/// "sub-consensus" system). +/// +/// This specific `MultiLocation` implementation uses a `Junctions` datatype which is a Rust `enum` +/// in order to make pattern matching easier. There are occasions where it is important to ensure +/// that a value is strictly an interior location, in those cases, `Junctions` may be used. +/// +/// The `MultiLocation` value of `Null` simply refers to the interpreting consensus system. +#[derive( + Copy, Clone, Decode, Encode, Eq, PartialEq, Ord, PartialOrd, Debug, TypeInfo, MaxEncodedLen, +)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct MultiLocation { + /// The number of parent junctions at the beginning of this `MultiLocation`. + pub parents: u8, + /// The interior (i.e. non-parent) junctions that this `MultiLocation` contains. + pub interior: Junctions, +} + +impl Default for MultiLocation { + fn default() -> Self { + Self { parents: 0, interior: Junctions::Here } + } +} + +/// A relative location which is constrained to be an interior location of the context. +/// +/// See also `MultiLocation`. +pub type InteriorMultiLocation = Junctions; + +impl MultiLocation { + /// Creates a new `MultiLocation` with the given number of parents and interior junctions. + pub fn new(parents: u8, interior: impl Into) -> MultiLocation { + MultiLocation { parents, interior: interior.into() } + } + + /// Consume `self` and return the equivalent `VersionedMultiLocation` value. + pub const fn into_versioned(self) -> VersionedMultiLocation { + VersionedMultiLocation::V3(self) + } + + /// Creates a new `MultiLocation` with 0 parents and a `Here` interior. + /// + /// The resulting `MultiLocation` can be interpreted as the "current consensus system". + pub const fn here() -> MultiLocation { + MultiLocation { parents: 0, interior: Junctions::Here } + } + + /// Creates a new `MultiLocation` which evaluates to the parent context. + pub const fn parent() -> MultiLocation { + MultiLocation { parents: 1, interior: Junctions::Here } + } + + /// Creates a new `MultiLocation` which evaluates to the grand parent context. + pub const fn grandparent() -> MultiLocation { + MultiLocation { parents: 2, interior: Junctions::Here } + } + + /// Creates a new `MultiLocation` with `parents` and an empty (`Here`) interior. + pub const fn ancestor(parents: u8) -> MultiLocation { + MultiLocation { parents, interior: Junctions::Here } + } + + /// Whether the `MultiLocation` has no parents and has a `Here` interior. + pub const fn is_here(&self) -> bool { + self.parents == 0 && self.interior.len() == 0 + } + + /// Remove the `NetworkId` value in any interior `Junction`s. + pub fn remove_network_id(&mut self) { + self.interior.remove_network_id(); + } + + /// Return a reference to the interior field. + pub fn interior(&self) -> &Junctions { + &self.interior + } + + /// Return a mutable reference to the interior field. + pub fn interior_mut(&mut self) -> &mut Junctions { + &mut self.interior + } + + /// Returns the number of `Parent` junctions at the beginning of `self`. + pub const fn parent_count(&self) -> u8 { + self.parents + } + + /// Returns boolean indicating whether `self` contains only the specified amount of + /// parents and no interior junctions. + pub const fn contains_parents_only(&self, count: u8) -> bool { + matches!(self.interior, Junctions::Here) && self.parents == count + } + + /// Returns the number of parents and junctions in `self`. + pub const fn len(&self) -> usize { + self.parent_count() as usize + self.interior.len() + } + + /// Returns the first interior junction, or `None` if the location is empty or contains only + /// parents. + pub fn first_interior(&self) -> Option<&Junction> { + self.interior.first() + } + + /// Returns last junction, or `None` if the location is empty or contains only parents. + pub fn last(&self) -> Option<&Junction> { + self.interior.last() + } + + /// Splits off the first interior junction, returning the remaining suffix (first item in tuple) + /// and the first element (second item in tuple) or `None` if it was empty. + pub fn split_first_interior(self) -> (MultiLocation, Option) { + let MultiLocation { parents, interior: junctions } = self; + let (suffix, first) = junctions.split_first(); + let multilocation = MultiLocation { parents, interior: suffix }; + (multilocation, first) + } + + /// Splits off the last interior junction, returning the remaining prefix (first item in tuple) + /// and the last element (second item in tuple) or `None` if it was empty or if `self` only + /// contains parents. + pub fn split_last_interior(self) -> (MultiLocation, Option) { + let MultiLocation { parents, interior: junctions } = self; + let (prefix, last) = junctions.split_last(); + let multilocation = MultiLocation { parents, interior: prefix }; + (multilocation, last) + } + + /// Mutates `self`, suffixing its interior junctions with `new`. Returns `Err` with `new` in + /// case of overflow. + pub fn push_interior(&mut self, new: impl Into) -> result::Result<(), Junction> { + self.interior.push(new) + } + + /// Mutates `self`, prefixing its interior junctions with `new`. Returns `Err` with `new` in + /// case of overflow. + pub fn push_front_interior( + &mut self, + new: impl Into, + ) -> result::Result<(), Junction> { + self.interior.push_front(new) + } + + /// Consumes `self` and returns a `MultiLocation` suffixed with `new`, or an `Err` with theoriginal value of + /// `self` in case of overflow. + pub fn pushed_with_interior( + self, + new: impl Into, + ) -> result::Result { + match self.interior.pushed_with(new) { + Ok(i) => Ok(MultiLocation { interior: i, parents: self.parents }), + Err((i, j)) => Err((MultiLocation { interior: i, parents: self.parents }, j)), + } + } + + /// Consumes `self` and returns a `MultiLocation` prefixed with `new`, or an `Err` with the original value of + /// `self` in case of overflow. + pub fn pushed_front_with_interior( + self, + new: impl Into, + ) -> result::Result { + match self.interior.pushed_front_with(new) { + Ok(i) => Ok(MultiLocation { interior: i, parents: self.parents }), + Err((i, j)) => Err((MultiLocation { interior: i, parents: self.parents }, j)), + } + } + + /// Returns the junction at index `i`, or `None` if the location is a parent or if the location + /// does not contain that many elements. + pub fn at(&self, i: usize) -> Option<&Junction> { + let num_parents = self.parents as usize; + if i < num_parents { + return None + } + self.interior.at(i - num_parents) + } + + /// Returns a mutable reference to the junction at index `i`, or `None` if the location is a + /// parent or if it doesn't contain that many elements. + pub fn at_mut(&mut self, i: usize) -> Option<&mut Junction> { + let num_parents = self.parents as usize; + if i < num_parents { + return None + } + self.interior.at_mut(i - num_parents) + } + + /// Decrements the parent count by 1. + pub fn dec_parent(&mut self) { + self.parents = self.parents.saturating_sub(1); + } + + /// Removes the first interior junction from `self`, returning it + /// (or `None` if it was empty or if `self` contains only parents). + pub fn take_first_interior(&mut self) -> Option { + self.interior.take_first() + } + + /// Removes the last element from `interior`, returning it (or `None` if it was empty or if + /// `self` only contains parents). + pub fn take_last(&mut self) -> Option { + self.interior.take_last() + } + + /// Ensures that `self` has the same number of parents as `prefix`, its junctions begins with + /// the junctions of `prefix` and that it has a single `Junction` item following. + /// If so, returns a reference to this `Junction` item. + /// + /// # Example + /// ```rust + /// # use xcm::v3::{Junctions::*, Junction::*, MultiLocation}; + /// # fn main() { + /// let mut m = MultiLocation::new(1, X2(PalletInstance(3), OnlyChild)); + /// assert_eq!( + /// m.match_and_split(&MultiLocation::new(1, X1(PalletInstance(3)))), + /// Some(&OnlyChild), + /// ); + /// assert_eq!(m.match_and_split(&MultiLocation::new(1, Here)), None); + /// # } + /// ``` + pub fn match_and_split(&self, prefix: &MultiLocation) -> Option<&Junction> { + if self.parents != prefix.parents { + return None + } + self.interior.match_and_split(&prefix.interior) + } + + pub fn starts_with(&self, prefix: &MultiLocation) -> bool { + self.parents == prefix.parents && self.interior.starts_with(&prefix.interior) + } + + /// Mutate `self` so that it is suffixed with `suffix`. + /// + /// Does not modify `self` and returns `Err` with `suffix` in case of overflow. + /// + /// # Example + /// ```rust + /// # use xcm::v3::{Junctions::*, Junction::*, MultiLocation, Parent}; + /// # fn main() { + /// let mut m: MultiLocation = (Parent, Parachain(21), 69u64).into(); + /// assert_eq!(m.append_with((Parent, PalletInstance(3))), Ok(())); + /// assert_eq!(m, MultiLocation::new(1, X2(Parachain(21), PalletInstance(3)))); + /// # } + /// ``` + pub fn append_with(&mut self, suffix: impl Into) -> Result<(), Self> { + let prefix = core::mem::replace(self, suffix.into()); + match self.prepend_with(prefix) { + Ok(()) => Ok(()), + Err(prefix) => Err(core::mem::replace(self, prefix)), + } + } + + /// Consume `self` and return its value suffixed with `suffix`. + /// + /// Returns `Err` with the original value of `self` and `suffix` in case of overflow. + /// + /// # Example + /// ```rust + /// # use xcm::v3::{Junctions::*, Junction::*, MultiLocation, Parent}; + /// # fn main() { + /// let mut m: MultiLocation = (Parent, Parachain(21), 69u64).into(); + /// let r = m.appended_with((Parent, PalletInstance(3))).unwrap(); + /// assert_eq!(r, MultiLocation::new(1, X2(Parachain(21), PalletInstance(3)))); + /// # } + /// ``` + pub fn appended_with(mut self, suffix: impl Into) -> Result { + match self.append_with(suffix) { + Ok(()) => Ok(self), + Err(suffix) => Err((self, suffix)), + } + } + + /// Mutate `self` so that it is prefixed with `prefix`. + /// + /// Does not modify `self` and returns `Err` with `prefix` in case of overflow. + /// + /// # Example + /// ```rust + /// # use xcm::v3::{Junctions::*, Junction::*, MultiLocation, Parent}; + /// # fn main() { + /// let mut m: MultiLocation = (Parent, Parent, PalletInstance(3)).into(); + /// assert_eq!(m.prepend_with((Parent, Parachain(21), OnlyChild)), Ok(())); + /// assert_eq!(m, MultiLocation::new(1, X1(PalletInstance(3)))); + /// # } + /// ``` + pub fn prepend_with(&mut self, prefix: impl Into) -> Result<(), Self> { + // prefix self (suffix) + // P .. P I .. I p .. p i .. i + let mut prefix = prefix.into(); + let prepend_interior = prefix.interior.len().saturating_sub(self.parents as usize); + let final_interior = self.interior.len().saturating_add(prepend_interior); + if final_interior > super::junctions::MAX_JUNCTIONS { + return Err(prefix) + } + let suffix_parents = (self.parents as usize).saturating_sub(prefix.interior.len()); + let final_parents = (prefix.parents as usize).saturating_add(suffix_parents); + if final_parents > 255 { + return Err(prefix) + } + + // cancel out the final item on the prefix interior for one of the suffix's parents. + while self.parents > 0 && prefix.take_last().is_some() { + self.dec_parent(); + } + + // now we have either removed all suffix's parents or prefix interior. + // this means we can combine the prefix's and suffix's remaining parents/interior since + // we know that with at least one empty, the overall order will be respected: + // prefix self (suffix) + // P .. P (I) p .. p i .. i => P + p .. (no I) i + // -- or -- + // P .. P I .. I (p) i .. i => P (no p) .. I + i + + self.parents = self.parents.saturating_add(prefix.parents); + for j in prefix.interior.into_iter().rev() { + self.push_front_interior(j) + .expect("final_interior no greater than MAX_JUNCTIONS; qed"); + } + Ok(()) + } + + /// Consume `self` and return its value prefixed with `prefix`. + /// + /// Returns `Err` with the original value of `self` and `prefix` in case of overflow. + /// + /// # Example + /// ```rust + /// # use xcm::v3::{Junctions::*, Junction::*, MultiLocation, Parent}; + /// # fn main() { + /// let m: MultiLocation = (Parent, Parent, PalletInstance(3)).into(); + /// let r = m.prepended_with((Parent, Parachain(21), OnlyChild)).unwrap(); + /// assert_eq!(r, MultiLocation::new(1, X1(PalletInstance(3)))); + /// # } + /// ``` + pub fn prepended_with(mut self, prefix: impl Into) -> Result { + match self.prepend_with(prefix) { + Ok(()) => Ok(self), + Err(prefix) => Err((self, prefix)), + } + } + + /// Mutate `self` so that it represents the same location from the point of view of `target`. + /// The context of `self` is provided as `context`. + /// + /// Does not modify `self` in case of overflow. + pub fn reanchor( + &mut self, + target: &MultiLocation, + context: InteriorMultiLocation, + ) -> Result<(), ()> { + // TODO: https://github.com/paritytech/polkadot/issues/4489 Optimize this. + + // 1. Use our `context` to figure out how the `target` would address us. + let inverted_target = context.invert_target(target)?; + + // 2. Prepend `inverted_target` to `self` to get self's location from the perspective of + // `target`. + self.prepend_with(inverted_target).map_err(|_| ())?; + + // 3. Given that we know some of `target` context, ensure that any parents in `self` are + // strictly needed. + self.simplify(target.interior()); + + Ok(()) + } + + /// Consume `self` and return a new value representing the same location from the point of view + /// of `target`. The context of `self` is provided as `context`. + /// + /// Returns the original `self` in case of overflow. + pub fn reanchored( + mut self, + target: &MultiLocation, + context: InteriorMultiLocation, + ) -> Result { + match self.reanchor(target, context) { + Ok(()) => Ok(self), + Err(()) => Err(self), + } + } + + /// Remove any unneeded parents/junctions in `self` based on the given context it will be + /// interpreted in. + pub fn simplify(&mut self, context: &Junctions) { + if context.len() < self.parents as usize { + // Not enough context + return + } + while self.parents > 0 { + let maybe = context.at(context.len() - (self.parents as usize)); + match (self.interior.first(), maybe) { + (Some(i), Some(j)) if i == j => { + self.interior.take_first(); + self.parents -= 1; + }, + _ => break, + } + } + } +} + +impl TryFrom for MultiLocation { + type Error = (); + fn try_from(x: OldMultiLocation) -> result::Result { + Ok(MultiLocation { parents: x.parents, interior: x.interior.try_into()? }) + } +} + +/// A unit struct which can be converted into a `MultiLocation` of `parents` value 1. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct Parent; +impl From for MultiLocation { + fn from(_: Parent) -> Self { + MultiLocation { parents: 1, interior: Junctions::Here } + } +} + +/// A tuple struct which can be converted into a `MultiLocation` of `parents` value 1 with the inner interior. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct ParentThen(pub Junctions); +impl From for MultiLocation { + fn from(ParentThen(interior): ParentThen) -> Self { + MultiLocation { parents: 1, interior } + } +} + +/// A unit struct which can be converted into a `MultiLocation` of the inner `parents` value. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct Ancestor(pub u8); +impl From for MultiLocation { + fn from(Ancestor(parents): Ancestor) -> Self { + MultiLocation { parents, interior: Junctions::Here } + } +} + +/// A unit struct which can be converted into a `MultiLocation` of the inner `parents` value and the inner interior. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct AncestorThen(pub u8, pub Interior); +impl> From> for MultiLocation { + fn from(AncestorThen(parents, interior): AncestorThen) -> Self { + MultiLocation { parents, interior: interior.into() } + } +} + +xcm_procedural::impl_conversion_functions_for_multilocation_v3!(); + +#[cfg(test)] +mod tests { + use crate::v3::prelude::*; + use parity_scale_codec::{Decode, Encode}; + + #[test] + fn conversion_works() { + let x: MultiLocation = Parent.into(); + assert_eq!(x, MultiLocation { parents: 1, interior: Here }); + // let x: MultiLocation = (Parent,).into(); + // assert_eq!(x, MultiLocation { parents: 1, interior: Here }); + // let x: MultiLocation = (Parent, Parent).into(); + // assert_eq!(x, MultiLocation { parents: 2, interior: Here }); + let x: MultiLocation = (Parent, Parent, OnlyChild).into(); + assert_eq!(x, MultiLocation { parents: 2, interior: OnlyChild.into() }); + let x: MultiLocation = OnlyChild.into(); + assert_eq!(x, MultiLocation { parents: 0, interior: OnlyChild.into() }); + let x: MultiLocation = (OnlyChild,).into(); + assert_eq!(x, MultiLocation { parents: 0, interior: OnlyChild.into() }); + } + + #[test] + fn simplify_basic_works() { + let mut location: MultiLocation = + (Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into(); + let context = X2(Parachain(1000), PalletInstance(42)); + let expected = GeneralIndex(69).into(); + location.simplify(&context); + assert_eq!(location, expected); + + let mut location: MultiLocation = (Parent, PalletInstance(42), GeneralIndex(69)).into(); + let context = X1(PalletInstance(42)); + let expected = GeneralIndex(69).into(); + location.simplify(&context); + assert_eq!(location, expected); + + let mut location: MultiLocation = (Parent, PalletInstance(42), GeneralIndex(69)).into(); + let context = X2(Parachain(1000), PalletInstance(42)); + let expected = GeneralIndex(69).into(); + location.simplify(&context); + assert_eq!(location, expected); + + let mut location: MultiLocation = + (Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into(); + let context = X3(OnlyChild, Parachain(1000), PalletInstance(42)); + let expected = GeneralIndex(69).into(); + location.simplify(&context); + assert_eq!(location, expected); + } + + #[test] + fn simplify_incompatible_location_fails() { + let mut location: MultiLocation = + (Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into(); + let context = X3(Parachain(1000), PalletInstance(42), GeneralIndex(42)); + let expected = + (Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into(); + location.simplify(&context); + assert_eq!(location, expected); + + let mut location: MultiLocation = + (Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into(); + let context = X1(Parachain(1000)); + let expected = + (Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into(); + location.simplify(&context); + assert_eq!(location, expected); + } + + #[test] + fn reanchor_works() { + let mut id: MultiLocation = (Parent, Parachain(1000), GeneralIndex(42)).into(); + let context = Parachain(2000).into(); + let target = (Parent, Parachain(1000)).into(); + let expected = GeneralIndex(42).into(); + id.reanchor(&target, context).unwrap(); + assert_eq!(id, expected); + } + + #[test] + fn encode_and_decode_works() { + let m = MultiLocation { + parents: 1, + interior: X2(Parachain(42), AccountIndex64 { network: None, index: 23 }), + }; + let encoded = m.encode(); + assert_eq!(encoded, [1, 2, 0, 168, 2, 0, 92].to_vec()); + let decoded = MultiLocation::decode(&mut &encoded[..]); + assert_eq!(decoded, Ok(m)); + } + + #[test] + fn match_and_split_works() { + let m = MultiLocation { + parents: 1, + interior: X2(Parachain(42), AccountIndex64 { network: None, index: 23 }), + }; + assert_eq!(m.match_and_split(&MultiLocation { parents: 1, interior: Here }), None); + assert_eq!( + m.match_and_split(&MultiLocation { parents: 1, interior: X1(Parachain(42)) }), + Some(&AccountIndex64 { network: None, index: 23 }) + ); + assert_eq!(m.match_and_split(&m), None); + } + + #[test] + fn append_with_works() { + let acc = AccountIndex64 { network: None, index: 23 }; + let mut m = MultiLocation { parents: 1, interior: X1(Parachain(42)) }; + assert_eq!(m.append_with(X2(PalletInstance(3), acc.clone())), Ok(())); + assert_eq!( + m, + MultiLocation { + parents: 1, + interior: X3(Parachain(42), PalletInstance(3), acc.clone()) + } + ); + + // cannot append to create overly long multilocation + let acc = AccountIndex64 { network: None, index: 23 }; + let m = MultiLocation { + parents: 254, + interior: X5(Parachain(42), OnlyChild, OnlyChild, OnlyChild, OnlyChild), + }; + let suffix: MultiLocation = (PalletInstance(3), acc.clone(), OnlyChild, OnlyChild).into(); + assert_eq!(m.clone().append_with(suffix.clone()), Err(suffix)); + } + + #[test] + fn prepend_with_works() { + let mut m = MultiLocation { + parents: 1, + interior: X2(Parachain(42), AccountIndex64 { network: None, index: 23 }), + }; + assert_eq!(m.prepend_with(MultiLocation { parents: 1, interior: X1(OnlyChild) }), Ok(())); + assert_eq!( + m, + MultiLocation { + parents: 1, + interior: X2(Parachain(42), AccountIndex64 { network: None, index: 23 }) + } + ); + + // cannot prepend to create overly long multilocation + let mut m = MultiLocation { parents: 254, interior: X1(Parachain(42)) }; + let prefix = MultiLocation { parents: 2, interior: Here }; + assert_eq!(m.prepend_with(prefix.clone()), Err(prefix)); + + let prefix = MultiLocation { parents: 1, interior: Here }; + assert_eq!(m.prepend_with(prefix), Ok(())); + assert_eq!(m, MultiLocation { parents: 255, interior: X1(Parachain(42)) }); + } + + #[test] + fn double_ended_ref_iteration_works() { + let m = X3(Parachain(1000), Parachain(3), PalletInstance(5)); + let mut iter = m.iter(); + + let first = iter.next().unwrap(); + assert_eq!(first, &Parachain(1000)); + let third = iter.next_back().unwrap(); + assert_eq!(third, &PalletInstance(5)); + let second = iter.next_back().unwrap(); + assert_eq!(iter.next(), None); + assert_eq!(iter.next_back(), None); + assert_eq!(second, &Parachain(3)); + + let res = Here + .pushed_with(first.clone()) + .unwrap() + .pushed_with(second.clone()) + .unwrap() + .pushed_with(third.clone()) + .unwrap(); + assert_eq!(m, res); + + // make sure there's no funny business with the 0 indexing + let m = Here; + let mut iter = m.iter(); + + assert_eq!(iter.next(), None); + assert_eq!(iter.next_back(), None); + } + + #[test] + fn conversion_from_other_types_works() { + use crate::v2; + use core::convert::TryInto; + + fn takes_multilocation>(_arg: Arg) {} + + takes_multilocation(Parent); + takes_multilocation(Here); + takes_multilocation(X1(Parachain(42))); + takes_multilocation((Ancestor(255), PalletInstance(8))); + takes_multilocation((Ancestor(5), Parachain(1), PalletInstance(3))); + takes_multilocation((Ancestor(2), Here)); + takes_multilocation(AncestorThen( + 3, + X2(Parachain(43), AccountIndex64 { network: None, index: 155 }), + )); + takes_multilocation((Parent, AccountId32 { network: None, id: [0; 32] })); + takes_multilocation((Parent, Here)); + takes_multilocation(ParentThen(X1(Parachain(75)))); + takes_multilocation([Parachain(100), PalletInstance(3)]); + + assert_eq!( + v2::MultiLocation::from(v2::Junctions::Here).try_into(), + Ok(MultiLocation::here()) + ); + assert_eq!(v2::MultiLocation::from(v2::Parent).try_into(), Ok(MultiLocation::parent())); + assert_eq!( + v2::MultiLocation::from((v2::Parent, v2::Parent, v2::Junction::GeneralIndex(42u128),)) + .try_into(), + Ok(MultiLocation { parents: 2, interior: X1(GeneralIndex(42u128)) }), + ); + } +} diff --git a/xcm/src/v3/traits.rs b/xcm/src/v3/traits.rs new file mode 100644 index 000000000000..6ccf01ad06c3 --- /dev/null +++ b/xcm/src/v3/traits.rs @@ -0,0 +1,541 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate 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. + +// Substrate 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 Cumulus. If not, see . + +//! Cross-Consensus Message format data structures. + +use crate::v2::Error as OldError; +use core::result; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +pub use sp_weights::Weight; + +use super::*; + +/// Error codes used in XCM. The first errors codes have explicit indices and are part of the XCM +/// format. Those trailing are merely part of the XCM implementation; there is no expectation that +/// they will retain the same index over time. +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] +pub enum Error { + // Errors that happen due to instructions being executed. These alone are defined in the + // XCM specification. + /// An arithmetic overflow happened. + #[codec(index = 0)] + Overflow, + /// The instruction is intentionally unsupported. + #[codec(index = 1)] + Unimplemented, + /// Origin Register does not contain a value value for a reserve transfer notification. + #[codec(index = 2)] + UntrustedReserveLocation, + /// Origin Register does not contain a value value for a teleport notification. + #[codec(index = 3)] + UntrustedTeleportLocation, + /// `MultiLocation` value too large to descend further. + #[codec(index = 4)] + LocationFull, + /// `MultiLocation` value ascend more parents than known ancestors of local location. + #[codec(index = 5)] + LocationNotInvertible, + /// The Origin Register does not contain a valid value for instruction. + #[codec(index = 6)] + BadOrigin, + /// The location parameter is not a valid value for the instruction. + #[codec(index = 7)] + InvalidLocation, + /// The given asset is not handled. + #[codec(index = 8)] + AssetNotFound, + /// An asset transaction (like withdraw or deposit) failed (typically due to type conversions). + #[codec(index = 9)] + FailedToTransactAsset(#[codec(skip)] &'static str), + /// An asset cannot be withdrawn, potentially due to lack of ownership, availability or rights. + #[codec(index = 10)] + NotWithdrawable, + /// An asset cannot be deposited under the ownership of a particular location. + #[codec(index = 11)] + LocationCannotHold, + /// Attempt to send a message greater than the maximum supported by the transport protocol. + #[codec(index = 12)] + ExceedsMaxMessageSize, + /// The given message cannot be translated into a format supported by the destination. + #[codec(index = 13)] + DestinationUnsupported, + /// Destination is routable, but there is some issue with the transport mechanism. + #[codec(index = 14)] + Transport(#[codec(skip)] &'static str), + /// Destination is known to be unroutable. + #[codec(index = 15)] + Unroutable, + /// Used by `ClaimAsset` when the given claim could not be recognized/found. + #[codec(index = 16)] + UnknownClaim, + /// Used by `Transact` when the functor cannot be decoded. + #[codec(index = 17)] + FailedToDecode, + /// Used by `Transact` to indicate that the given weight limit could be breached by the functor. + #[codec(index = 18)] + MaxWeightInvalid, + /// Used by `BuyExecution` when the Holding Register does not contain payable fees. + #[codec(index = 19)] + NotHoldingFees, + /// Used by `BuyExecution` when the fees declared to purchase weight are insufficient. + #[codec(index = 20)] + TooExpensive, + /// Used by the `Trap` instruction to force an error intentionally. Its code is included. + #[codec(index = 21)] + Trap(u64), + /// Used by `ExpectAsset`, `ExpectError` and `ExpectOrigin` when the expectation was not true. + #[codec(index = 22)] + ExpectationFalse, + /// The provided pallet index was not found. + #[codec(index = 23)] + PalletNotFound, + /// The given pallet's name is different to that expected. + #[codec(index = 24)] + NameMismatch, + /// The given pallet's version has an incompatible version to that expected. + #[codec(index = 25)] + VersionIncompatible, + /// The given operation would lead to an overflow of the Holding Register. + #[codec(index = 26)] + HoldingWouldOverflow, + /// The message was unable to be exported. + #[codec(index = 27)] + ExportError, + /// `MultiLocation` value failed to be reanchored. + #[codec(index = 28)] + ReanchorFailed, + /// No deal is possible under the given constraints. + #[codec(index = 29)] + NoDeal, + /// Fees were required which the origin could not pay. + #[codec(index = 30)] + FeesNotMet, + /// Some other error with locking. + #[codec(index = 31)] + LockError, + /// The state was not in a condition where the operation was valid to make. + #[codec(index = 32)] + NoPermission, + /// The universal location of the local consensus is improper. + #[codec(index = 33)] + Unanchored, + /// An asset cannot be deposited, probably because (too much of) it already exists. + #[codec(index = 34)] + NotDepositable, + + // Errors that happen prior to instructions being executed. These fall outside of the XCM spec. + /// XCM version not able to be handled. + UnhandledXcmVersion, + /// Execution of the XCM would potentially result in a greater weight used than weight limit. + WeightLimitReached(Weight), + /// The XCM did not pass the barrier condition for execution. + /// + /// The barrier condition differs on different chains and in different circumstances, but + /// generally it means that the conditions surrounding the message were not such that the chain + /// considers the message worth spending time executing. Since most chains lift the barrier to + /// execution on appropriate payment, presentation of an NFT voucher, or based on the message + /// origin, it means that none of those were the case. + Barrier, + /// The weight of an XCM message is not computable ahead of execution. + WeightNotComputable, + /// Recursion stack limit reached + ExceedsStackLimit, +} + +impl MaxEncodedLen for Error { + fn max_encoded_len() -> usize { + // TODO: max_encoded_len doesn't quite work here as it tries to take notice of the fields + // marked `codec(skip)`. We can hard-code it with the right answer for now. + 1 + } +} + +impl TryFrom for Error { + type Error = (); + fn try_from(old_error: OldError) -> result::Result { + use OldError::*; + Ok(match old_error { + Overflow => Self::Overflow, + Unimplemented => Self::Unimplemented, + UntrustedReserveLocation => Self::UntrustedReserveLocation, + UntrustedTeleportLocation => Self::UntrustedTeleportLocation, + MultiLocationFull => Self::LocationFull, + MultiLocationNotInvertible => Self::LocationNotInvertible, + BadOrigin => Self::BadOrigin, + InvalidLocation => Self::InvalidLocation, + AssetNotFound => Self::AssetNotFound, + FailedToTransactAsset(s) => Self::FailedToTransactAsset(s), + NotWithdrawable => Self::NotWithdrawable, + LocationCannotHold => Self::LocationCannotHold, + ExceedsMaxMessageSize => Self::ExceedsMaxMessageSize, + DestinationUnsupported => Self::DestinationUnsupported, + Transport(s) => Self::Transport(s), + Unroutable => Self::Unroutable, + UnknownClaim => Self::UnknownClaim, + FailedToDecode => Self::FailedToDecode, + MaxWeightInvalid => Self::MaxWeightInvalid, + NotHoldingFees => Self::NotHoldingFees, + TooExpensive => Self::TooExpensive, + Trap(i) => Self::Trap(i), + _ => return Err(()), + }) + } +} + +impl From for Error { + fn from(e: SendError) -> Self { + match e { + SendError::NotApplicable | SendError::Unroutable | SendError::MissingArgument => + Error::Unroutable, + SendError::Transport(s) => Error::Transport(s), + SendError::DestinationUnsupported => Error::DestinationUnsupported, + SendError::ExceedsMaxMessageSize => Error::ExceedsMaxMessageSize, + SendError::Fees => Error::FeesNotMet, + } + } +} + +pub type Result = result::Result<(), Error>; + +/// Outcome of an XCM execution. +#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] +pub enum Outcome { + /// Execution completed successfully; given weight was used. + Complete(Weight), + /// Execution started, but did not complete successfully due to the given error; given weight was used. + Incomplete(Weight, Error), + /// Execution did not start due to the given error. + Error(Error), +} + +impl Outcome { + pub fn ensure_complete(self) -> Result { + match self { + Outcome::Complete(_) => Ok(()), + Outcome::Incomplete(_, e) => Err(e), + Outcome::Error(e) => Err(e), + } + } + pub fn ensure_execution(self) -> result::Result { + match self { + Outcome::Complete(w) => Ok(w), + Outcome::Incomplete(w, _) => Ok(w), + Outcome::Error(e) => Err(e), + } + } + /// How much weight was used by the XCM execution attempt. + pub fn weight_used(&self) -> Weight { + match self { + Outcome::Complete(w) => *w, + Outcome::Incomplete(w, _) => *w, + Outcome::Error(_) => Weight::zero(), + } + } +} + +pub trait PreparedMessage { + fn weight_of(&self) -> Weight; +} + +/// Type of XCM message executor. +pub trait ExecuteXcm { + type Prepared: PreparedMessage; + fn prepare(message: Xcm) -> result::Result>; + fn execute( + origin: impl Into, + pre: Self::Prepared, + hash: XcmHash, + weight_credit: Weight, + ) -> Outcome; + + /// Execute some XCM `message` with the message `hash` from `origin` using no more than `weight_limit` weight. + /// The weight limit is a basic hard-limit and the implementation may place further restrictions or requirements + /// on weight and other aspects. + fn execute_xcm( + origin: impl Into, + message: Xcm, + hash: XcmHash, + weight_limit: Weight, + ) -> Outcome { + let origin = origin.into(); + log::debug!( + target: "xcm::execute_xcm", + "origin: {:?}, message: {:?}, weight_limit: {:?}", + origin, + message, + weight_limit, + ); + Self::execute_xcm_in_credit(origin, message, hash, weight_limit, Weight::zero()) + } + + /// Execute some XCM `message` with the message `hash` from `origin` using no more than `weight_limit` weight. + /// + /// Some amount of `weight_credit` may be provided which, depending on the implementation, may allow + /// execution without associated payment. + fn execute_xcm_in_credit( + origin: impl Into, + message: Xcm, + hash: XcmHash, + weight_limit: Weight, + weight_credit: Weight, + ) -> Outcome { + let pre = match Self::prepare(message) { + Ok(x) => x, + Err(_) => return Outcome::Error(Error::WeightNotComputable), + }; + let xcm_weight = pre.weight_of(); + if xcm_weight.any_gt(weight_limit) { + return Outcome::Error(Error::WeightLimitReached(xcm_weight)) + } + Self::execute(origin, pre, hash, weight_credit) + } + + /// Deduct some `fees` to the sovereign account of the given `location` and place them as per + /// the convention for fees. + fn charge_fees(location: impl Into, fees: MultiAssets) -> Result; +} + +pub enum Weightless {} +impl PreparedMessage for Weightless { + fn weight_of(&self) -> Weight { + unreachable!() + } +} + +impl ExecuteXcm for () { + type Prepared = Weightless; + fn prepare(message: Xcm) -> result::Result> { + Err(message) + } + fn execute(_: impl Into, _: Self::Prepared, _: XcmHash, _: Weight) -> Outcome { + unreachable!() + } + fn charge_fees(_location: impl Into, _fees: MultiAssets) -> Result { + Err(Error::Unimplemented) + } +} + +/// Error result value when attempting to send an XCM message. +#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, scale_info::TypeInfo)] +pub enum SendError { + /// The message and destination combination was not recognized as being reachable. + /// + /// This is not considered fatal: if there are alternative transport routes available, then + /// they may be attempted. + NotApplicable, + /// Destination is routable, but there is some issue with the transport mechanism. This is + /// considered fatal. + /// A human-readable explanation of the specific issue is provided. + Transport(#[codec(skip)] &'static str), + /// Destination is known to be unroutable. This is considered fatal. + Unroutable, + /// The given message cannot be translated into a format that the destination can be expected + /// to interpret. + DestinationUnsupported, + /// Message could not be sent due to its size exceeding the maximum allowed by the transport + /// layer. + ExceedsMaxMessageSize, + /// A needed argument is `None` when it should be `Some`. + MissingArgument, + /// Fees needed to be paid in order to send the message and they were unavailable. + Fees, +} + +/// A hash type for identifying messages. +pub type XcmHash = [u8; 32]; + +/// Result value when attempting to send an XCM message. +pub type SendResult = result::Result<(T, MultiAssets), SendError>; + +pub trait Unwrappable { + type Inner; + fn none() -> Self; + fn some(i: Self::Inner) -> Self; + fn take(self) -> Option; +} + +impl Unwrappable for Option { + type Inner = T; + fn none() -> Self { + None + } + fn some(i: Self::Inner) -> Self { + Some(i) + } + fn take(self) -> Option { + self + } +} + +/// Utility for sending an XCM message to a given location. +/// +/// These can be amalgamated in tuples to form sophisticated routing systems. In tuple format, each +/// router might return `NotApplicable` to pass the execution to the next sender item. Note that +/// each `NotApplicable` might alter the destination and the XCM message for to the next router. +/// +/// # Example +/// ```rust +/// # use parity_scale_codec::Encode; +/// # use xcm::v3::{prelude::*, Weight}; +/// # use xcm::VersionedXcm; +/// # use std::convert::Infallible; +/// +/// /// A sender that only passes the message through and does nothing. +/// struct Sender1; +/// impl SendXcm for Sender1 { +/// type Ticket = Infallible; +/// fn validate(_: &mut Option, _: &mut Option>) -> SendResult { +/// Err(SendError::NotApplicable) +/// } +/// fn deliver(_: Infallible) -> Result { +/// unreachable!() +/// } +/// } +/// +/// /// A sender that accepts a message that has an X2 junction, otherwise stops the routing. +/// struct Sender2; +/// impl SendXcm for Sender2 { +/// type Ticket = (); +/// fn validate(destination: &mut Option, message: &mut Option>) -> SendResult<()> { +/// match destination.as_ref().ok_or(SendError::MissingArgument)? { +/// MultiLocation { parents: 0, interior: X2(j1, j2) } => Ok(((), MultiAssets::new())), +/// _ => Err(SendError::Unroutable), +/// } +/// } +/// fn deliver(_: ()) -> Result { +/// Ok([0; 32]) +/// } +/// } +/// +/// /// A sender that accepts a message from a parent, passing through otherwise. +/// struct Sender3; +/// impl SendXcm for Sender3 { +/// type Ticket = (); +/// fn validate(destination: &mut Option, message: &mut Option>) -> SendResult<()> { +/// match destination.as_ref().ok_or(SendError::MissingArgument)? { +/// MultiLocation { parents: 1, interior: Here } => Ok(((), MultiAssets::new())), +/// _ => Err(SendError::NotApplicable), +/// } +/// } +/// fn deliver(_: ()) -> Result { +/// Ok([0; 32]) +/// } +/// } +/// +/// // A call to send via XCM. We don't really care about this. +/// # fn main() { +/// let call: Vec = ().encode(); +/// let message = Xcm(vec![Instruction::Transact { +/// origin_kind: OriginKind::Superuser, +/// require_weight_at_most: Weight::zero(), +/// call: call.into(), +/// }]); +/// let message_hash = message.using_encoded(sp_io::hashing::blake2_256); +/// +/// // Sender2 will block this. +/// assert!(send_xcm::<(Sender1, Sender2, Sender3)>(Parent.into(), message.clone()).is_err()); +/// +/// // Sender3 will catch this. +/// assert!(send_xcm::<(Sender1, Sender3)>(Parent.into(), message.clone()).is_ok()); +/// # } +/// ``` +pub trait SendXcm { + /// Intermediate value which connects the two phaases of the send operation. + type Ticket; + + /// Check whether the given `_message` is deliverable to the given `_destination` and if so + /// determine the cost which will be paid by this chain to do so, returning a `Validated` token + /// which can be used to enact delivery. + /// + /// The `destination` and `message` must be `Some` (or else an error will be returned) and they + /// may only be consumed if the `Err` is not `NotApplicable`. + /// + /// If it is not a destination which can be reached with this type but possibly could by others, + /// then this *MUST* return `NotApplicable`. Any other error will cause the tuple + /// implementation to exit early without trying other type fields. + fn validate( + destination: &mut Option, + message: &mut Option>, + ) -> SendResult; + + /// Actually carry out the delivery operation for a previously validated message sending. + fn deliver(ticket: Self::Ticket) -> result::Result; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl SendXcm for Tuple { + for_tuples! { type Ticket = (#( Option ),* ); } + + fn validate( + destination: &mut Option, + message: &mut Option>, + ) -> SendResult { + let mut maybe_cost: Option = None; + let one_ticket: Self::Ticket = (for_tuples! { #( + if maybe_cost.is_some() { + None + } else { + match Tuple::validate(destination, message) { + Err(SendError::NotApplicable) => None, + Err(e) => { return Err(e) }, + Ok((v, c)) => { + maybe_cost = Some(c); + Some(v) + }, + } + } + ),* }); + if let Some(cost) = maybe_cost { + Ok((one_ticket, cost)) + } else { + Err(SendError::NotApplicable) + } + } + + fn deliver(one_ticket: Self::Ticket) -> result::Result { + for_tuples!( #( + if let Some(validated) = one_ticket.Tuple { + return Tuple::deliver(validated); + } + )* ); + Err(SendError::Unroutable) + } +} + +/// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps +/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +pub fn validate_send(dest: MultiLocation, msg: Xcm<()>) -> SendResult { + T::validate(&mut Some(dest), &mut Some(msg)) +} + +/// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps +/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// +/// Returns either `Ok` with the price of the delivery, or `Err` with the reason why the message +/// could not be sent. +/// +/// Generally you'll want to validate and get the price first to ensure that the sender can pay it +/// before actually doing the delivery. +pub fn send_xcm( + dest: MultiLocation, + msg: Xcm<()>, +) -> result::Result<(XcmHash, MultiAssets), SendError> { + let (ticket, price) = T::validate(&mut Some(dest), &mut Some(msg))?; + let hash = T::deliver(ticket)?; + Ok((hash, price)) +} diff --git a/xcm/xcm-builder/Cargo.toml b/xcm/xcm-builder/Cargo.toml index d3a3b0fda4fd..6caa6cddd409 100644 --- a/xcm/xcm-builder/Cargo.toml +++ b/xcm/xcm-builder/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true version.workspace = true [dependencies] +impl-trait-for-tuples = "0.2.1" parity-scale-codec = { version = "3.1.5", default-features = false, features = ["derive"] } scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } xcm = { path = "..", default-features = false } @@ -27,6 +28,8 @@ sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" } pallet-xcm = { path = "../pallet-xcm" } polkadot-runtime-parachains = { path = "../../runtime/parachains" } +assert_matches = "1.5.0" + [features] default = ["std"] runtime-benchmarks = [ @@ -44,6 +47,7 @@ std = [ "sp-io/std", "sp-runtime/std", "frame-support/std", + "frame-system/std", "polkadot-parachain/std", "pallet-transaction-payment/std", ] diff --git a/xcm/xcm-builder/src/asset_conversion.rs b/xcm/xcm-builder/src/asset_conversion.rs new file mode 100644 index 000000000000..1a13f1ffb444 --- /dev/null +++ b/xcm/xcm-builder/src/asset_conversion.rs @@ -0,0 +1,149 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! Adapters to work with `frame_support::traits::tokens::fungibles` through XCM. + +use frame_support::traits::Get; +use sp_std::{borrow::Borrow, marker::PhantomData, prelude::*, result}; +use xcm::latest::prelude::*; +use xcm_executor::traits::{Convert, Error as MatchError, MatchesFungibles, MatchesNonFungibles}; + +/// Converter struct implementing `AssetIdConversion` converting a numeric asset ID (must be `TryFrom/TryInto`) into +/// a `GeneralIndex` junction, prefixed by some `MultiLocation` value. The `MultiLocation` value will typically be a +/// `PalletInstance` junction. +pub struct AsPrefixedGeneralIndex( + PhantomData<(Prefix, AssetId, ConvertAssetId)>, +); +impl, AssetId: Clone, ConvertAssetId: Convert> + Convert for AsPrefixedGeneralIndex +{ + fn convert_ref(id: impl Borrow) -> result::Result { + let prefix = Prefix::get(); + let id = id.borrow(); + if prefix.parent_count() != id.parent_count() || + prefix + .interior() + .iter() + .enumerate() + .any(|(index, junction)| id.interior().at(index) != Some(junction)) + { + return Err(()) + } + match id.interior().at(prefix.interior().len()) { + Some(Junction::GeneralIndex(id)) => ConvertAssetId::convert_ref(id), + _ => Err(()), + } + } + fn reverse_ref(what: impl Borrow) -> result::Result { + let mut location = Prefix::get(); + let id = ConvertAssetId::reverse_ref(what)?; + location.push_interior(Junction::GeneralIndex(id)).map_err(|_| ())?; + Ok(location) + } +} + +pub struct ConvertedConcreteId( + PhantomData<(AssetId, Balance, ConvertAssetId, ConvertOther)>, +); +impl< + AssetId: Clone, + Balance: Clone, + ConvertAssetId: Convert, + ConvertBalance: Convert, + > MatchesFungibles + for ConvertedConcreteId +{ + fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), MatchError> { + let (amount, id) = match (&a.fun, &a.id) { + (Fungible(ref amount), Concrete(ref id)) => (amount, id), + _ => return Err(MatchError::AssetNotFound), + }; + let what = + ConvertAssetId::convert_ref(id).map_err(|_| MatchError::AssetIdConversionFailed)?; + let amount = ConvertBalance::convert_ref(amount) + .map_err(|_| MatchError::AmountToBalanceConversionFailed)?; + Ok((what, amount)) + } +} +impl< + ClassId: Clone, + InstanceId: Clone, + ConvertClassId: Convert, + ConvertInstanceId: Convert, + > MatchesNonFungibles + for ConvertedConcreteId +{ + fn matches_nonfungibles(a: &MultiAsset) -> result::Result<(ClassId, InstanceId), MatchError> { + let (instance, class) = match (&a.fun, &a.id) { + (NonFungible(ref instance), Concrete(ref class)) => (instance, class), + _ => return Err(MatchError::AssetNotFound), + }; + let what = + ConvertClassId::convert_ref(class).map_err(|_| MatchError::AssetIdConversionFailed)?; + let instance = ConvertInstanceId::convert_ref(instance) + .map_err(|_| MatchError::InstanceConversionFailed)?; + Ok((what, instance)) + } +} + +pub struct ConvertedAbstractId( + PhantomData<(AssetId, Balance, ConvertAssetId, ConvertOther)>, +); +impl< + AssetId: Clone, + Balance: Clone, + ConvertAssetId: Convert<[u8; 32], AssetId>, + ConvertBalance: Convert, + > MatchesFungibles + for ConvertedAbstractId +{ + fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), MatchError> { + let (amount, id) = match (&a.fun, &a.id) { + (Fungible(ref amount), Abstract(ref id)) => (amount, id), + _ => return Err(MatchError::AssetNotFound), + }; + let what = + ConvertAssetId::convert_ref(id).map_err(|_| MatchError::AssetIdConversionFailed)?; + let amount = ConvertBalance::convert_ref(amount) + .map_err(|_| MatchError::AmountToBalanceConversionFailed)?; + Ok((what, amount)) + } +} +impl< + ClassId: Clone, + InstanceId: Clone, + ConvertClassId: Convert<[u8; 32], ClassId>, + ConvertInstanceId: Convert, + > MatchesNonFungibles + for ConvertedAbstractId +{ + fn matches_nonfungibles(a: &MultiAsset) -> result::Result<(ClassId, InstanceId), MatchError> { + let (instance, class) = match (&a.fun, &a.id) { + (NonFungible(ref instance), Abstract(ref class)) => (instance, class), + _ => return Err(MatchError::AssetNotFound), + }; + let what = + ConvertClassId::convert_ref(class).map_err(|_| MatchError::AssetIdConversionFailed)?; + let instance = ConvertInstanceId::convert_ref(instance) + .map_err(|_| MatchError::InstanceConversionFailed)?; + Ok((what, instance)) + } +} + +#[deprecated = "Use `ConvertedConcreteId` instead"] +pub type ConvertedConcreteAssetId = ConvertedConcreteId; +#[deprecated = "Use `ConvertedAbstractId` instead"] +pub type ConvertedAbstractAssetId = ConvertedAbstractId; diff --git a/xcm/xcm-builder/src/barriers.rs b/xcm/xcm-builder/src/barriers.rs index ce830554f3b5..bdc7f6811edd 100644 --- a/xcm/xcm-builder/src/barriers.rs +++ b/xcm/xcm-builder/src/barriers.rs @@ -16,11 +16,18 @@ //! Various implementations for `ShouldExecute`. -use frame_support::{ensure, traits::Contains}; +use frame_support::{ + ensure, + traits::{Contains, Get}, +}; use polkadot_parachain::primitives::IsSystem; use sp_std::{marker::PhantomData, result::Result}; use xcm::latest::{ - Instruction::*, Junction, Junctions, MultiLocation, Weight, WeightLimit::*, Xcm, + Instruction::{self, *}, + InteriorMultiLocation, Junction, Junctions, + Junctions::X1, + MultiLocation, Weight, + WeightLimit::*, }; use xcm_executor::traits::{OnResponse, ShouldExecute}; @@ -33,16 +40,16 @@ pub struct TakeWeightCredit; impl ShouldExecute for TakeWeightCredit { fn should_execute( _origin: &MultiLocation, - _message: &mut Xcm, + _instructions: &mut [Instruction], max_weight: Weight, weight_credit: &mut Weight, ) -> Result<(), ()> { log::trace!( target: "xcm::barriers", - "TakeWeightCredit origin: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}", - _origin, _message, max_weight, weight_credit, + "TakeWeightCredit origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}", + _origin, _instructions, max_weight, weight_credit, ); - *weight_credit = weight_credit.checked_sub(max_weight).ok_or(())?; + *weight_credit = weight_credit.checked_sub(&max_weight).ok_or(())?; Ok(()) } } @@ -56,17 +63,21 @@ pub struct AllowTopLevelPaidExecutionFrom(PhantomData); impl> ShouldExecute for AllowTopLevelPaidExecutionFrom { fn should_execute( origin: &MultiLocation, - message: &mut Xcm, + instructions: &mut [Instruction], max_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { log::trace!( target: "xcm::barriers", - "AllowTopLevelPaidExecutionFrom origin: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}", - origin, message, max_weight, _weight_credit, + "AllowTopLevelPaidExecutionFrom origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}", + origin, instructions, max_weight, _weight_credit, ); + ensure!(T::contains(origin), ()); - let mut iter = message.0.iter_mut(); + // We will read up to 5 instructions. This allows up to 3 `ClearOrigin` instructions. We + // allow for more than one since anything beyond the first is a no-op and it's conceivable + // that composition of operations might result in more than one being appended. + let mut iter = instructions.iter_mut().take(5); let i = iter.next().ok_or(())?; match i { ReceiveTeleportedAsset(..) | @@ -80,8 +91,10 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFro i = iter.next().ok_or(())?; } match i { - BuyExecution { weight_limit: Limited(ref mut weight), .. } if *weight >= max_weight => { - *weight = max_weight; + BuyExecution { weight_limit: Limited(ref mut weight), .. } + if weight.all_gte(max_weight) => + { + *weight = weight.max(max_weight); Ok(()) }, BuyExecution { ref mut weight_limit, .. } if weight_limit == &Unlimited => { @@ -93,26 +106,150 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFro } } -/// Allows execution from any origin that is contained in `T` (i.e. `T::Contains(origin)`) without any payments. -/// Use only for executions from trusted origin groups. +/// A derivative barrier, which scans the first `MaxPrefixes` instructions for origin-alterers and +/// then evaluates `should_execute` of the `InnerBarrier` based on the remaining instructions and +/// the newly computed origin. +/// +/// This effectively allows for the possibility of distinguishing an origin which is acting as a +/// router for its derivative locations (or as a bridge for a remote location) and an origin which +/// is actually trying to send a message for itself. In the former case, the message will be +/// prefixed with origin-mutating instructions. +/// +/// Any barriers which should be interpreted based on the computed origin rather than the original +/// message origin should be subject to this. This is the case for most barriers since the +/// effective origin is generally more important than the routing origin. Any other barriers, and +/// especially those which should be interpreted only the routing origin should not be subject to +/// this. +/// +/// E.g. +/// ```nocompile +/// type MyBarrier = ( +/// TakeWeightCredit, +/// AllowTopLevelPaidExecutionFrom, +/// WithComputedOrigin<( +/// AllowTopLevelPaidExecutionFrom, +/// AllowUnpaidExecutionFrom, +/// AllowSubscriptionsFrom, +/// AllowKnownQueryResponses, +/// )>, +/// ); +/// ``` +/// +/// In the above example, `AllowUnpaidExecutionFrom` appears once underneath +/// `WithComputedOrigin`. This is in order to distinguish between messages which are notionally +/// from a derivative location of `ParentLocation` but that just happened to be sent via +/// `ParentLocaction` rather than messages that were sent by the parent. +/// +/// Similarly `AllowTopLevelPaidExecutionFrom` appears twice: once inside of `WithComputedOrigin` +/// where we provide the list of origins which are derivative origins, and then secondly outside +/// of `WithComputedOrigin` where we provide the list of locations which are direct origins. It's +/// reasonable for these lists to be merged into one and that used both inside and out. +/// +/// Finally, we see `AllowSubscriptionsFrom` and `AllowKnownQueryResponses` are both inside of +/// `WithComputedOrigin`. This means that if a message begins with origin-mutating instructions, +/// then it must be the finally computed origin which we accept subscriptions or expect a query +/// response from. For example, even if an origin appeared in the `AllowedSubscribers` list, we +/// would ignore this rule if it began with origin mutators and they changed the origin to something +/// which was not on the list. +pub struct WithComputedOrigin( + PhantomData<(InnerBarrier, LocalUniversal, MaxPrefixes)>, +); +impl< + InnerBarrier: ShouldExecute, + LocalUniversal: Get, + MaxPrefixes: Get, + > ShouldExecute for WithComputedOrigin +{ + fn should_execute( + origin: &MultiLocation, + instructions: &mut [Instruction], + max_weight: Weight, + weight_credit: &mut Weight, + ) -> Result<(), ()> { + log::trace!( + target: "xcm::barriers", + "WithComputedOrigin origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}", + origin, instructions, max_weight, weight_credit, + ); + let mut actual_origin = *origin; + let mut skipped = 0; + // NOTE: We do not check the validity of `UniversalOrigin` here, meaning that a malicious + // origin could place a `UniversalOrigin` in order to spoof some location which gets free + // execution. This technical could get it past the barrier condition, but the execution + // would instantly fail since the first instruction would cause an error with the + // invalid UniversalOrigin. + while skipped < MaxPrefixes::get() as usize { + match instructions.get(skipped) { + Some(UniversalOrigin(new_global)) => { + // Note the origin is *relative to local consensus*! So we need to escape local + // consensus with the `parents` before diving in into the `universal_location`. + actual_origin = X1(*new_global).relative_to(&LocalUniversal::get()); + }, + Some(DescendOrigin(j)) => { + actual_origin.append_with(*j).map_err(|_| ())?; + }, + _ => break, + } + skipped += 1; + } + InnerBarrier::should_execute( + &actual_origin, + &mut instructions[skipped..], + max_weight, + weight_credit, + ) + } +} + +/// Allows execution from any origin that is contained in `T` (i.e. `T::Contains(origin)`). +/// +/// Use only for executions from completely trusted origins, from which no unpermissioned messages +/// can be sent. pub struct AllowUnpaidExecutionFrom(PhantomData); impl> ShouldExecute for AllowUnpaidExecutionFrom { fn should_execute( origin: &MultiLocation, - _message: &mut Xcm, + instructions: &mut [Instruction], _max_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { log::trace!( target: "xcm::barriers", - "AllowUnpaidExecutionFrom origin: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}", - origin, _message, _max_weight, _weight_credit, + "AllowUnpaidExecutionFrom origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}", + origin, instructions, _max_weight, _weight_credit, ); ensure!(T::contains(origin), ()); Ok(()) } } +/// Allows execution from any origin that is contained in `T` (i.e. `T::Contains(origin)`) if the +/// message begins with the instruction `UnpaidExecution`. +/// +/// Use only for executions from trusted origin groups. +pub struct AllowExplicitUnpaidExecutionFrom(PhantomData); +impl> ShouldExecute for AllowExplicitUnpaidExecutionFrom { + fn should_execute( + origin: &MultiLocation, + instructions: &mut [Instruction], + max_weight: Weight, + _weight_credit: &mut Weight, + ) -> Result<(), ()> { + log::trace!( + target: "xcm::barriers", + "AllowExplicitUnpaidExecutionFrom origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}", + origin, instructions, max_weight, _weight_credit, + ); + ensure!(T::contains(origin), ()); + match instructions.first() { + Some(UnpaidExecution { weight_limit: Limited(m), .. }) if m.all_gte(max_weight) => + Ok(()), + Some(UnpaidExecution { weight_limit: Unlimited, .. }) => Ok(()), + _ => Err(()), + } + } +} + /// Allows a message only if it is from a system-level child parachain. pub struct IsChildSystemParachain(PhantomData); impl> Contains for IsChildSystemParachain { @@ -130,42 +267,43 @@ pub struct AllowKnownQueryResponses(PhantomData ShouldExecute for AllowKnownQueryResponses { fn should_execute( origin: &MultiLocation, - message: &mut Xcm, + instructions: &mut [Instruction], _max_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { log::trace!( target: "xcm::barriers", - "AllowKnownQueryResponses origin: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}", - origin, message, _max_weight, _weight_credit, + "AllowKnownQueryResponses origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}", + origin, instructions, _max_weight, _weight_credit, ); - match message.0.first() { - Some(QueryResponse { query_id, .. }) - if ResponseHandler::expecting_response(origin, *query_id) => + ensure!(instructions.len() == 1, ()); + match instructions.first() { + Some(QueryResponse { query_id, querier, .. }) + if ResponseHandler::expecting_response(origin, *query_id, querier.as_ref()) => Ok(()), _ => Err(()), } } } -/// Allows execution from `origin` if it is just a straight `SubscribeVerison` or +/// Allows execution from `origin` if it is just a straight `SubscribeVersion` or /// `UnsubscribeVersion` instruction. pub struct AllowSubscriptionsFrom(PhantomData); impl> ShouldExecute for AllowSubscriptionsFrom { fn should_execute( origin: &MultiLocation, - message: &mut Xcm, + instructions: &mut [Instruction], _max_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { log::trace!( target: "xcm::barriers", - "AllowSubscriptionsFrom origin: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}", - origin, message, _max_weight, _weight_credit, + "AllowSubscriptionsFrom origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}", + origin, instructions, _max_weight, _weight_credit, ); ensure!(T::contains(origin), ()); - match (message.0.len(), message.0.first()) { - (1, Some(SubscribeVersion { .. })) | (1, Some(UnsubscribeVersion)) => Ok(()), + match instructions { + &mut [SubscribeVersion { .. } | UnsubscribeVersion] => Ok(()), _ => Err(()), } } diff --git a/xcm/xcm-builder/src/currency_adapter.rs b/xcm/xcm-builder/src/currency_adapter.rs index f231bf1094b4..2815d8bd3c47 100644 --- a/xcm/xcm-builder/src/currency_adapter.rs +++ b/xcm/xcm-builder/src/currency_adapter.rs @@ -16,10 +16,11 @@ //! Adapters to work with `frame_support::traits::Currency` through XCM. +use super::MintLocation; use frame_support::traits::{ExistenceRequirement::AllowDeath, Get, WithdrawReasons}; use sp_runtime::traits::CheckedSub; use sp_std::{marker::PhantomData, result}; -use xcm::latest::{Error as XcmError, MultiAsset, MultiLocation, Result}; +use xcm::latest::{Error as XcmError, MultiAsset, MultiLocation, Result, XcmContext}; use xcm_executor::{ traits::{Convert, MatchesFungible, TransactAsset}, Assets, @@ -67,10 +68,13 @@ impl From for XcmError { /// /// messages from the parent (relay chain). /// pub type LocationConverter = (ParentIsPreset); /// +/// /// Just a dummy implementation of `Currency`. Normally this would be `Balances`. +/// pub type CurrencyImpl = (); +/// /// /// Final currency adapter. This can be used in `xcm::Config` to specify how asset related transactions happen. /// pub type AssetTransactor = CurrencyAdapter< -/// // Use this balance type: -/// u128, +/// // Use this `Currency` impl instance: +/// CurrencyImpl, /// // The matcher: use the currency when the asset is a concrete asset in our relay chain. /// IsConcrete, /// // The local converter: default account of the parent relay chain. @@ -86,67 +90,108 @@ pub struct CurrencyAdapter, Matcher: MatchesFungible, AccountIdConverter: Convert, + AccountId: Clone, // can't get away without it since Currency is generic over it. + CheckedAccount: Get>, + > CurrencyAdapter +{ + fn can_accrue_checked(_checked_account: AccountId, _amount: Currency::Balance) -> Result { + Ok(()) + } + fn can_reduce_checked(checked_account: AccountId, amount: Currency::Balance) -> Result { + let new_balance = Currency::free_balance(&checked_account) + .checked_sub(&amount) + .ok_or(XcmError::NotWithdrawable)?; + Currency::ensure_can_withdraw( + &checked_account, + amount, + WithdrawReasons::TRANSFER, + new_balance, + ) + .map_err(|_| XcmError::NotWithdrawable) + } + fn accrue_checked(checked_account: AccountId, amount: Currency::Balance) { + Currency::deposit_creating(&checked_account, amount); + Currency::deactivate(amount); + } + fn reduce_checked(checked_account: AccountId, amount: Currency::Balance) { + let ok = + Currency::withdraw(&checked_account, amount, WithdrawReasons::TRANSFER, AllowDeath) + .is_ok(); + if ok { + Currency::reactivate(amount); + } else { + frame_support::defensive!( + "`can_check_in` must have returned `true` immediately prior; qed" + ); + } + } +} + +impl< Currency: frame_support::traits::Currency, + Matcher: MatchesFungible, + AccountIdConverter: Convert, AccountId: Clone, // can't get away without it since Currency is generic over it. - CheckedAccount: Get>, + CheckedAccount: Get>, > TransactAsset for CurrencyAdapter { - fn can_check_in(_origin: &MultiLocation, what: &MultiAsset) -> Result { + fn can_check_in(_origin: &MultiLocation, what: &MultiAsset, _context: &XcmContext) -> Result { log::trace!(target: "xcm::currency_adapter", "can_check_in origin: {:?}, what: {:?}", _origin, what); // Check we handle this asset. let amount: Currency::Balance = Matcher::matches_fungible(what).ok_or(Error::AssetNotFound)?; - if let Some(checked_account) = CheckedAccount::get() { - let new_balance = Currency::free_balance(&checked_account) - .checked_sub(&amount) - .ok_or(XcmError::NotWithdrawable)?; - Currency::ensure_can_withdraw( - &checked_account, - amount, - WithdrawReasons::TRANSFER, - new_balance, - ) - .map_err(|_| XcmError::NotWithdrawable)?; + match CheckedAccount::get() { + Some((checked_account, MintLocation::Local)) => + Self::can_reduce_checked(checked_account, amount), + Some((checked_account, MintLocation::NonLocal)) => + Self::can_accrue_checked(checked_account, amount), + None => Ok(()), } - Ok(()) } - fn check_in(_origin: &MultiLocation, what: &MultiAsset) { + fn check_in(_origin: &MultiLocation, what: &MultiAsset, _context: &XcmContext) { log::trace!(target: "xcm::currency_adapter", "check_in origin: {:?}, what: {:?}", _origin, what); if let Some(amount) = Matcher::matches_fungible(what) { - if let Some(checked_account) = CheckedAccount::get() { - let ok = Currency::withdraw( - &checked_account, - amount, - WithdrawReasons::TRANSFER, - AllowDeath, - ) - .is_ok(); - if ok { - Currency::reactivate(amount); - } - debug_assert!( - ok, - "`can_check_in` must have returned `true` immediately prior; qed" - ); + match CheckedAccount::get() { + Some((checked_account, MintLocation::Local)) => + Self::reduce_checked(checked_account, amount), + Some((checked_account, MintLocation::NonLocal)) => + Self::accrue_checked(checked_account, amount), + None => (), } } } - fn check_out(_dest: &MultiLocation, what: &MultiAsset) { + fn can_check_out(_dest: &MultiLocation, what: &MultiAsset, _context: &XcmContext) -> Result { + log::trace!(target: "xcm::currency_adapter", "check_out dest: {:?}, what: {:?}", _dest, what); + let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotFound)?; + match CheckedAccount::get() { + Some((checked_account, MintLocation::Local)) => + Self::can_accrue_checked(checked_account, amount), + Some((checked_account, MintLocation::NonLocal)) => + Self::can_reduce_checked(checked_account, amount), + None => Ok(()), + } + } + + fn check_out(_dest: &MultiLocation, what: &MultiAsset, _context: &XcmContext) { log::trace!(target: "xcm::currency_adapter", "check_out dest: {:?}, what: {:?}", _dest, what); if let Some(amount) = Matcher::matches_fungible(what) { - if let Some(checked_account) = CheckedAccount::get() { - Currency::deposit_creating(&checked_account, amount); - Currency::deactivate(amount); + match CheckedAccount::get() { + Some((checked_account, MintLocation::Local)) => + Self::accrue_checked(checked_account, amount), + Some((checked_account, MintLocation::NonLocal)) => + Self::reduce_checked(checked_account, amount), + None => (), } } } - fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> Result { + fn deposit_asset(what: &MultiAsset, who: &MultiLocation, _context: &XcmContext) -> Result { log::trace!(target: "xcm::currency_adapter", "deposit_asset what: {:?}, who: {:?}", what, who); // Check we handle this asset. let amount = Matcher::matches_fungible(&what).ok_or(Error::AssetNotFound)?; @@ -156,7 +201,11 @@ impl< Ok(()) } - fn withdraw_asset(what: &MultiAsset, who: &MultiLocation) -> result::Result { + fn withdraw_asset( + what: &MultiAsset, + who: &MultiLocation, + _maybe_context: Option<&XcmContext>, + ) -> result::Result { log::trace!(target: "xcm::currency_adapter", "withdraw_asset what: {:?}, who: {:?}", what, who); // Check we handle this asset. let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotFound)?; @@ -171,6 +220,7 @@ impl< asset: &MultiAsset, from: &MultiLocation, to: &MultiLocation, + _context: &XcmContext, ) -> result::Result { log::trace!(target: "xcm::currency_adapter", "internal_transfer_asset asset: {:?}, from: {:?}, to: {:?}", asset, from, to); let amount = Matcher::matches_fungible(asset).ok_or(Error::AssetNotFound)?; diff --git a/xcm/xcm-builder/src/filter_asset_location.rs b/xcm/xcm-builder/src/filter_asset_location.rs index a1e1763d0e40..1701988d624b 100644 --- a/xcm/xcm-builder/src/filter_asset_location.rs +++ b/xcm/xcm-builder/src/filter_asset_location.rs @@ -14,28 +14,29 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! Various implementations of `FilterAssetLocation`. +//! Various implementations of `ContainsPair`. -use frame_support::traits::Get; +use frame_support::traits::{ContainsPair, Get}; use sp_std::marker::PhantomData; use xcm::latest::{AssetId::Concrete, MultiAsset, MultiAssetFilter, MultiLocation}; -use xcm_executor::traits::FilterAssetLocation; /// Accepts an asset iff it is a native asset. pub struct NativeAsset; -impl FilterAssetLocation for NativeAsset { - fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { - log::trace!(target: "xcm::filter_asset_location", "NativeAsset asset: {:?}, origin: {:?}", asset, origin); +impl ContainsPair for NativeAsset { + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + log::trace!(target: "xcm::contains", "NativeAsset asset: {:?}, origin: {:?}", asset, origin); matches!(asset.id, Concrete(ref id) if id == origin) } } /// Accepts an asset if it is contained in the given `T`'s `Get` implementation. pub struct Case(PhantomData); -impl> FilterAssetLocation for Case { - fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { - log::trace!(target: "xcm::filter_asset_location", "Case asset: {:?}, origin: {:?}", asset, origin); +impl> ContainsPair + for Case +{ + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + log::trace!(target: "xcm::contains", "Case asset: {:?}, origin: {:?}", asset, origin); let (a, o) = T::get(); - a.contains(asset) && &o == origin + a.matches(asset) && &o == origin } } diff --git a/xcm/xcm-builder/src/fungibles_adapter.rs b/xcm/xcm-builder/src/fungibles_adapter.rs index d06f92d97db9..c20fc9926712 100644 --- a/xcm/xcm-builder/src/fungibles_adapter.rs +++ b/xcm/xcm-builder/src/fungibles_adapter.rs @@ -17,97 +17,11 @@ //! Adapters to work with `frame_support::traits::tokens::fungibles` through XCM. use frame_support::traits::{tokens::fungibles, Contains, Get}; -use sp_std::{borrow::Borrow, marker::PhantomData, prelude::*, result}; -use xcm::latest::{ - AssetId::{Abstract, Concrete}, - Error as XcmError, - Fungibility::Fungible, - Junction, MultiAsset, MultiLocation, Result, -}; +use sp_std::{marker::PhantomData, prelude::*, result}; +use xcm::latest::prelude::*; use xcm_executor::traits::{Convert, Error as MatchError, MatchesFungibles, TransactAsset}; -/// Converter struct implementing `AssetIdConversion` converting a numeric asset ID (must be `TryFrom/TryInto`) into -/// a `GeneralIndex` junction, prefixed by some `MultiLocation` value. The `MultiLocation` value will typically be a -/// `PalletInstance` junction. -pub struct AsPrefixedGeneralIndex( - PhantomData<(Prefix, AssetId, ConvertAssetId)>, -); -impl, AssetId: Clone, ConvertAssetId: Convert> - Convert for AsPrefixedGeneralIndex -{ - fn convert_ref(id: impl Borrow) -> result::Result { - let prefix = Prefix::get(); - let id = id.borrow(); - if prefix.parent_count() != id.parent_count() || - prefix - .interior() - .iter() - .enumerate() - .any(|(index, junction)| id.interior().at(index) != Some(junction)) - { - return Err(()) - } - match id.interior().at(prefix.interior().len()) { - Some(Junction::GeneralIndex(id)) => ConvertAssetId::convert_ref(id), - _ => Err(()), - } - } - fn reverse_ref(what: impl Borrow) -> result::Result { - let mut location = Prefix::get(); - let id = ConvertAssetId::reverse_ref(what)?; - location.push_interior(Junction::GeneralIndex(id)).map_err(|_| ())?; - Ok(location) - } -} - -pub struct ConvertedConcreteAssetId( - PhantomData<(AssetId, Balance, ConvertAssetId, ConvertBalance)>, -); -impl< - AssetId: Clone, - Balance: Clone, - ConvertAssetId: Convert, - ConvertBalance: Convert, - > MatchesFungibles - for ConvertedConcreteAssetId -{ - fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), MatchError> { - let (amount, id) = match (&a.fun, &a.id) { - (Fungible(ref amount), Concrete(ref id)) => (amount, id), - _ => return Err(MatchError::AssetNotFound), - }; - let what = - ConvertAssetId::convert_ref(id).map_err(|_| MatchError::AssetIdConversionFailed)?; - let amount = ConvertBalance::convert_ref(amount) - .map_err(|_| MatchError::AmountToBalanceConversionFailed)?; - Ok((what, amount)) - } -} - -pub struct ConvertedAbstractAssetId( - PhantomData<(AssetId, Balance, ConvertAssetId, ConvertBalance)>, -); -impl< - AssetId: Clone, - Balance: Clone, - ConvertAssetId: Convert, AssetId>, - ConvertBalance: Convert, - > MatchesFungibles - for ConvertedAbstractAssetId -{ - fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), MatchError> { - let (amount, id) = match (&a.fun, &a.id) { - (Fungible(ref amount), Abstract(ref id)) => (amount, id), - _ => return Err(MatchError::AssetNotFound), - }; - let what = - ConvertAssetId::convert_ref(id).map_err(|_| MatchError::AssetIdConversionFailed)?; - let amount = ConvertBalance::convert_ref(amount) - .map_err(|_| MatchError::AmountToBalanceConversionFailed)?; - Ok((what, amount)) - } -} - +/// `TransactAsset` implementation to convert a `fungibles` implementation to become usable in XCM. pub struct FungiblesTransferAdapter( PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>, ); @@ -122,6 +36,7 @@ impl< what: &MultiAsset, from: &MultiLocation, to: &MultiLocation, + _context: &XcmContext, ) -> result::Result { log::trace!( target: "xcm::fungibles_adapter", @@ -140,6 +55,83 @@ impl< } } +/// The location which is allowed to mint a particular asset. +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum MintLocation { + /// This chain is allowed to mint the asset. When we track teleports of the asset we ensure that + /// no more of the asset returns back to the chain than has been sent out. + Local, + /// This chain is not allowed to mint the asset. When we track teleports of the asset we ensure + /// that no more of the asset is sent out from the chain than has been previously received. + NonLocal, +} + +/// Simple trait to indicate whether an asset is subject to having its teleportation into and out of +/// this chain recorded and if so in what `MintLocation`. +/// +/// The overall purpose of asset-checking is to ensure either no more assets are teleported into a +/// chain than the outstanding balance of assets which were previously teleported out (as in the +/// case of locally-minted assets); or that no more assets are teleported out of a chain than the +/// outstanding balance of assets which have previously been teleported in (as in the case of chains +/// where the `asset` is not minted locally). +pub trait AssetChecking { + /// Return the teleportation asset-checking policy for the given `asset`. `None` implies no + /// checking. Otherwise the policy detailed by the inner `MintLocation` should be respected by + /// teleportation. + fn asset_checking(asset: &AssetId) -> Option; +} + +/// Implementation of `AssetChecking` which subjects no assets to having their teleportations +/// recorded. +pub struct NoChecking; +impl AssetChecking for NoChecking { + fn asset_checking(_: &AssetId) -> Option { + None + } +} + +/// Implementation of `AssetChecking` which subjects a given set of assets `T` to having their +/// teleportations recorded with a `MintLocation::Local`. +pub struct LocalMint(sp_std::marker::PhantomData); +impl> AssetChecking for LocalMint { + fn asset_checking(asset: &AssetId) -> Option { + match T::contains(asset) { + true => Some(MintLocation::Local), + false => None, + } + } +} + +/// Implementation of `AssetChecking` which subjects a given set of assets `T` to having their +/// teleportations recorded with a `MintLocation::NonLocal`. +pub struct NonLocalMint(sp_std::marker::PhantomData); +impl> AssetChecking for NonLocalMint { + fn asset_checking(asset: &AssetId) -> Option { + match T::contains(asset) { + true => Some(MintLocation::NonLocal), + false => None, + } + } +} + +/// Implementation of `AssetChecking` which subjects a given set of assets `L` to having their +/// teleportations recorded with a `MintLocation::Local` and a second set of assets `R` to having +/// their teleportations recorded with a `MintLocation::NonLocal`. +pub struct DualMint(sp_std::marker::PhantomData<(L, R)>); +impl, R: Contains> AssetChecking + for DualMint +{ + fn asset_checking(asset: &AssetId) -> Option { + if L::contains(asset) { + Some(MintLocation::Local) + } else if R::contains(asset) { + Some(MintLocation::NonLocal) + } else { + None + } + } +} + pub struct FungiblesMutateAdapter< Assets, Matcher, @@ -148,12 +140,48 @@ pub struct FungiblesMutateAdapter< CheckAsset, CheckingAccount, >(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>); + impl< Assets: fungibles::Mutate, Matcher: MatchesFungibles, AccountIdConverter: Convert, AccountId: Clone, // can't get away without it since Currency is generic over it. - CheckAsset: Contains, + CheckAsset: AssetChecking, + CheckingAccount: Get, + > + FungiblesMutateAdapter +{ + fn can_accrue_checked(asset_id: Assets::AssetId, amount: Assets::Balance) -> XcmResult { + let checking_account = CheckingAccount::get(); + Assets::can_deposit(asset_id, &checking_account, amount, true) + .into_result() + .map_err(|_| XcmError::NotDepositable) + } + fn can_reduce_checked(asset_id: Assets::AssetId, amount: Assets::Balance) -> XcmResult { + let checking_account = CheckingAccount::get(); + Assets::can_withdraw(asset_id, &checking_account, amount) + .into_result() + .map_err(|_| XcmError::NotWithdrawable) + .map(|_| ()) + } + fn accrue_checked(asset_id: Assets::AssetId, amount: Assets::Balance) { + let checking_account = CheckingAccount::get(); + let ok = Assets::mint_into(asset_id, &checking_account, amount).is_ok(); + debug_assert!(ok, "`can_accrue_checked` must have returned `true` immediately prior; qed"); + } + fn reduce_checked(asset_id: Assets::AssetId, amount: Assets::Balance) { + let checking_account = CheckingAccount::get(); + let ok = Assets::burn_from(asset_id, &checking_account, amount).is_ok(); + debug_assert!(ok, "`can_reduce_checked` must have returned `true` immediately prior; qed"); + } +} + +impl< + Assets: fungibles::Mutate, + Matcher: MatchesFungibles, + AccountIdConverter: Convert, + AccountId: Clone, // can't get away without it since Currency is generic over it. + CheckAsset: AssetChecking, CheckingAccount: Get, > TransactAsset for FungiblesMutateAdapter< @@ -165,7 +193,11 @@ impl< CheckingAccount, > { - fn can_check_in(_origin: &MultiLocation, what: &MultiAsset) -> Result { + fn can_check_in( + _origin: &MultiLocation, + what: &MultiAsset, + _context: &XcmContext, + ) -> XcmResult { log::trace!( target: "xcm::fungibles_adapter", "can_check_in origin: {:?}, what: {:?}", @@ -173,50 +205,71 @@ impl< ); // Check we handle this asset. let (asset_id, amount) = Matcher::matches_fungibles(what)?; - if CheckAsset::contains(&asset_id) { - // This is an asset whose teleports we track. - let checking_account = CheckingAccount::get(); - Assets::can_withdraw(asset_id, &checking_account, amount) - .into_result() - .map_err(|_| XcmError::NotWithdrawable)?; + match CheckAsset::asset_checking(&asset_id) { + // We track this asset's teleports to ensure no more come in than have gone out. + Some(MintLocation::Local) => Self::can_reduce_checked(asset_id, amount), + // We track this asset's teleports to ensure no more go out than have come in. + Some(MintLocation::NonLocal) => Self::can_accrue_checked(asset_id, amount), + _ => Ok(()), } - Ok(()) } - fn check_in(_origin: &MultiLocation, what: &MultiAsset) { + fn check_in(_origin: &MultiLocation, what: &MultiAsset, _context: &XcmContext) { log::trace!( target: "xcm::fungibles_adapter", "check_in origin: {:?}, what: {:?}", _origin, what ); if let Ok((asset_id, amount)) = Matcher::matches_fungibles(what) { - if CheckAsset::contains(&asset_id) { - let checking_account = CheckingAccount::get(); - let ok = Assets::burn_from(asset_id, &checking_account, amount).is_ok(); - debug_assert!( - ok, - "`can_check_in` must have returned `true` immediately prior; qed" - ); + match CheckAsset::asset_checking(&asset_id) { + // We track this asset's teleports to ensure no more come in than have gone out. + Some(MintLocation::Local) => Self::reduce_checked(asset_id, amount), + // We track this asset's teleports to ensure no more go out than have come in. + Some(MintLocation::NonLocal) => Self::accrue_checked(asset_id, amount), + _ => (), } } } - fn check_out(_dest: &MultiLocation, what: &MultiAsset) { + fn can_check_out( + _origin: &MultiLocation, + what: &MultiAsset, + _context: &XcmContext, + ) -> XcmResult { + log::trace!( + target: "xcm::fungibles_adapter", + "can_check_in origin: {:?}, what: {:?}", + _origin, what + ); + // Check we handle this asset. + let (asset_id, amount) = Matcher::matches_fungibles(what)?; + match CheckAsset::asset_checking(&asset_id) { + // We track this asset's teleports to ensure no more come in than have gone out. + Some(MintLocation::Local) => Self::can_accrue_checked(asset_id, amount), + // We track this asset's teleports to ensure no more go out than have come in. + Some(MintLocation::NonLocal) => Self::can_reduce_checked(asset_id, amount), + _ => Ok(()), + } + } + + fn check_out(_dest: &MultiLocation, what: &MultiAsset, _context: &XcmContext) { log::trace!( target: "xcm::fungibles_adapter", "check_out dest: {:?}, what: {:?}", _dest, what ); if let Ok((asset_id, amount)) = Matcher::matches_fungibles(what) { - if CheckAsset::contains(&asset_id) { - let checking_account = CheckingAccount::get(); - let ok = Assets::mint_into(asset_id, &checking_account, amount).is_ok(); - debug_assert!(ok, "`mint_into` cannot generally fail; qed"); + match CheckAsset::asset_checking(&asset_id) { + // We track this asset's teleports to ensure no more come in than have gone out. + Some(MintLocation::Local) => Self::accrue_checked(asset_id, amount), + // We track this asset's teleports to ensure no more go out than have come in. + Some(MintLocation::NonLocal) => Self::reduce_checked(asset_id, amount), + _ => (), } } } - fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> Result { + fn deposit_asset(what: &MultiAsset, who: &MultiLocation, _context: &XcmContext) -> XcmResult { log::trace!( target: "xcm::fungibles_adapter", "deposit_asset what: {:?}, who: {:?}", @@ -233,6 +286,7 @@ impl< fn withdraw_asset( what: &MultiAsset, who: &MultiLocation, + _maybe_context: Option<&XcmContext>, ) -> result::Result { log::trace!( target: "xcm::fungibles_adapter", @@ -262,12 +316,23 @@ impl< Matcher: MatchesFungibles, AccountIdConverter: Convert, AccountId: Clone, // can't get away without it since Currency is generic over it. - CheckAsset: Contains, + CheckAsset: AssetChecking, CheckingAccount: Get, > TransactAsset for FungiblesAdapter { - fn can_check_in(origin: &MultiLocation, what: &MultiAsset) -> Result { + fn can_check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { + FungiblesMutateAdapter::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + >::can_check_in(origin, what, context) + } + + fn check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) { FungiblesMutateAdapter::< Assets, Matcher, @@ -275,10 +340,10 @@ impl< AccountId, CheckAsset, CheckingAccount, - >::can_check_in(origin, what) + >::check_in(origin, what, context) } - fn check_in(origin: &MultiLocation, what: &MultiAsset) { + fn can_check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { FungiblesMutateAdapter::< Assets, Matcher, @@ -286,10 +351,10 @@ impl< AccountId, CheckAsset, CheckingAccount, - >::check_in(origin, what) + >::can_check_out(dest, what, context) } - fn check_out(dest: &MultiLocation, what: &MultiAsset) { + fn check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) { FungiblesMutateAdapter::< Assets, Matcher, @@ -297,10 +362,10 @@ impl< AccountId, CheckAsset, CheckingAccount, - >::check_out(dest, what) + >::check_out(dest, what, context) } - fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> Result { + fn deposit_asset(what: &MultiAsset, who: &MultiLocation, context: &XcmContext) -> XcmResult { FungiblesMutateAdapter::< Assets, Matcher, @@ -308,12 +373,13 @@ impl< AccountId, CheckAsset, CheckingAccount, - >::deposit_asset(what, who) + >::deposit_asset(what, who, context) } fn withdraw_asset( what: &MultiAsset, who: &MultiLocation, + maybe_context: Option<&XcmContext>, ) -> result::Result { FungiblesMutateAdapter::< Assets, @@ -322,16 +388,17 @@ impl< AccountId, CheckAsset, CheckingAccount, - >::withdraw_asset(what, who) + >::withdraw_asset(what, who, maybe_context) } fn internal_transfer_asset( what: &MultiAsset, from: &MultiLocation, to: &MultiLocation, + context: &XcmContext, ) -> result::Result { FungiblesTransferAdapter::::internal_transfer_asset( - what, from, to, + what, from, to, context ) } } diff --git a/xcm/xcm-builder/src/lib.rs b/xcm/xcm-builder/src/lib.rs index aa55c56d2cd7..5f26394f4075 100644 --- a/xcm/xcm-builder/src/lib.rs +++ b/xcm/xcm-builder/src/lib.rs @@ -20,8 +20,6 @@ #![cfg_attr(not(feature = "std"), no_std)] -#[cfg(test)] -mod mock; #[cfg(test)] mod tests; @@ -31,7 +29,7 @@ pub mod test_utils; mod location_conversion; pub use location_conversion::{ Account32Hash, AccountId32Aliases, AccountKey20Aliases, ChildParachainConvertsVia, - LocationInverter, ParentIsPreset, SiblingParachainConvertsVia, + ParentIsPreset, SiblingParachainConvertsVia, }; mod origin_conversion; @@ -42,10 +40,16 @@ pub use origin_conversion::{ SignedToAccountId32, SovereignSignedViaLocation, }; +mod asset_conversion; +pub use asset_conversion::{AsPrefixedGeneralIndex, ConvertedAbstractId, ConvertedConcreteId}; +#[allow(deprecated)] +pub use asset_conversion::{ConvertedAbstractAssetId, ConvertedConcreteAssetId}; + mod barriers; pub use barriers::{ - AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, - AllowUnpaidExecutionFrom, IsChildSystemParachain, TakeWeightCredit, + AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, + AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, IsChildSystemParachain, + TakeWeightCredit, WithComputedOrigin, }; mod currency_adapter; @@ -53,19 +57,29 @@ pub use currency_adapter::CurrencyAdapter; mod fungibles_adapter; pub use fungibles_adapter::{ - AsPrefixedGeneralIndex, ConvertedAbstractAssetId, ConvertedConcreteAssetId, FungiblesAdapter, - FungiblesMutateAdapter, FungiblesTransferAdapter, + AssetChecking, DualMint, FungiblesAdapter, FungiblesMutateAdapter, FungiblesTransferAdapter, + LocalMint, MintLocation, NoChecking, NonLocalMint, +}; + +mod nonfungibles_adapter; +pub use nonfungibles_adapter::{ + NonFungiblesAdapter, NonFungiblesMutateAdapter, NonFungiblesTransferAdapter, }; mod weight; -#[allow(deprecated)] -pub use weight::FixedRateOfConcreteFungible; pub use weight::{ FixedRateOfFungible, FixedWeightBounds, TakeRevenue, UsingComponents, WeightInfoBounds, }; -mod matches_fungible; -pub use matches_fungible::{IsAbstract, IsConcrete}; +mod matches_token; +pub use matches_token::{IsAbstract, IsConcrete}; mod filter_asset_location; pub use filter_asset_location::{Case, NativeAsset}; + +mod universal_exports; +pub use universal_exports::{ + BridgeBlobDispatcher, BridgeMessage, DispatchBlob, DispatchBlobError, ExporterFor, HaulBlob, + HaulBlobExporter, NetworkExportTable, SovereignPaidRemoteExporter, UnpaidLocalExporter, + UnpaidRemoteExporter, +}; diff --git a/xcm/xcm-builder/src/location_conversion.rs b/xcm/xcm-builder/src/location_conversion.rs index ef41d697433f..a3ced7ba6d90 100644 --- a/xcm/xcm-builder/src/location_conversion.rs +++ b/xcm/xcm-builder/src/location_conversion.rs @@ -19,11 +19,11 @@ use parity_scale_codec::{Decode, Encode}; use sp_io::hashing::blake2_256; use sp_runtime::traits::{AccountIdConversion, TrailingZeroInput}; use sp_std::{borrow::Borrow, marker::PhantomData}; -use xcm::latest::{Junction::*, Junctions::*, MultiLocation, NetworkId, Parent}; -use xcm_executor::traits::{Convert, InvertLocation}; +use xcm::latest::prelude::*; +use xcm_executor::traits::Convert; pub struct Account32Hash(PhantomData<(Network, AccountId)>); -impl, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone> +impl>, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone> Convert for Account32Hash { fn convert_ref(location: impl Borrow) -> Result { @@ -107,15 +107,12 @@ impl + Into + AccountIdConversion, AccountId: /// Extracts the `AccountId32` from the passed `location` if the network matches. pub struct AccountId32Aliases(PhantomData<(Network, AccountId)>); -impl, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone> +impl>, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone> Convert for AccountId32Aliases { fn convert(location: MultiLocation) -> Result { let id = match location { - MultiLocation { - parents: 0, - interior: X1(AccountId32 { id, network: NetworkId::Any }), - } => id, + MultiLocation { parents: 0, interior: X1(AccountId32 { id, network: None }) } => id, MultiLocation { parents: 0, interior: X1(AccountId32 { id, network }) } if network == Network::get() => id, @@ -130,15 +127,12 @@ impl, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone } pub struct AccountKey20Aliases(PhantomData<(Network, AccountId)>); -impl, AccountId: From<[u8; 20]> + Into<[u8; 20]> + Clone> +impl>, AccountId: From<[u8; 20]> + Into<[u8; 20]> + Clone> Convert for AccountKey20Aliases { fn convert(location: MultiLocation) -> Result { let key = match location { - MultiLocation { - parents: 0, - interior: X1(AccountKey20 { key, network: NetworkId::Any }), - } => key, + MultiLocation { parents: 0, interior: X1(AccountKey20 { key, network: None }) } => key, MultiLocation { parents: 0, interior: X1(AccountKey20 { key, network }) } if network == Network::get() => key, @@ -153,69 +147,19 @@ impl, AccountId: From<[u8; 20]> + Into<[u8; 20]> + Clone } } -/// Simple location inverter; give it this location's ancestry and it'll figure out the inverted -/// location. -/// -/// # Example -/// ## Network Topology -/// ```txt -/// v Source -/// Relay -> Para 1 -> Account20 -/// -> Para 2 -> Account32 -/// ^ Target -/// ``` -/// ```rust -/// # use frame_support::parameter_types; -/// # use xcm::latest::{MultiLocation, Junction::*, Junctions::{self, *}, NetworkId::Any}; -/// # use xcm_builder::LocationInverter; -/// # use xcm_executor::traits::InvertLocation; -/// # fn main() { -/// parameter_types!{ -/// pub Ancestry: MultiLocation = X2( -/// Parachain(1), -/// AccountKey20 { network: Any, key: Default::default() }, -/// ).into(); -/// } -/// -/// let input = MultiLocation::new(2, X2(Parachain(2), AccountId32 { network: Any, id: Default::default() })); -/// let inverted = LocationInverter::::invert_location(&input); -/// assert_eq!(inverted, Ok(MultiLocation::new( -/// 2, -/// X2(Parachain(1), AccountKey20 { network: Any, key: Default::default() }), -/// ))); -/// # } -/// ``` -pub struct LocationInverter(PhantomData); -impl> InvertLocation for LocationInverter { - fn ancestry() -> MultiLocation { - Ancestry::get() - } - fn invert_location(location: &MultiLocation) -> Result { - let mut ancestry = Ancestry::get(); - let mut junctions = Here; - for _ in 0..location.parent_count() { - junctions = junctions - .pushed_with(ancestry.take_first_interior().unwrap_or(OnlyChild)) - .map_err(|_| ())?; - } - let parents = location.interior().len() as u8; - Ok(MultiLocation::new(parents, junctions)) - } -} - #[cfg(test)] mod tests { use super::*; use frame_support::parameter_types; - use xcm::latest::{Junction, NetworkId::Any}; + use xcm::latest::Junction; fn account20() -> Junction { - AccountKey20 { network: Any, key: Default::default() } + AccountKey20 { network: None, key: Default::default() } } fn account32() -> Junction { - AccountId32 { network: Any, id: Default::default() } + AccountId32 { network: None, id: Default::default() } } // Network Topology @@ -227,17 +171,17 @@ mod tests { // Inputs and outputs written as file paths: // // input location (source to target): ../../../para_2/account32_default - // ancestry (root to source): para_1/account20_default/account20_default + // context (root to source): para_1/account20_default/account20_default // => // output (target to source): ../../para_1/account20_default/account20_default #[test] fn inverter_works_in_tree() { parameter_types! { - pub Ancestry: MultiLocation = X3(Parachain(1), account20(), account20()).into(); + pub UniversalLocation: InteriorMultiLocation = X3(Parachain(1), account20(), account20()); } let input = MultiLocation::new(3, X2(Parachain(2), account32())); - let inverted = LocationInverter::::invert_location(&input).unwrap(); + let inverted = UniversalLocation::get().invert_target(&input).unwrap(); assert_eq!(inverted, MultiLocation::new(2, X3(Parachain(1), account20(), account20()))); } @@ -246,13 +190,13 @@ mod tests { // Relay -> Para 1 -> SmartContract -> Account // ^ Target #[test] - fn inverter_uses_ancestry_as_inverted_location() { + fn inverter_uses_context_as_inverted_location() { parameter_types! { - pub Ancestry: MultiLocation = X2(account20(), account20()).into(); + pub UniversalLocation: InteriorMultiLocation = X2(account20(), account20()); } let input = MultiLocation::grandparent(); - let inverted = LocationInverter::::invert_location(&input).unwrap(); + let inverted = UniversalLocation::get().invert_target(&input).unwrap(); assert_eq!(inverted, X2(account20(), account20()).into()); } @@ -261,24 +205,24 @@ mod tests { // Relay -> Para 1 -> CollectivePallet -> Plurality // ^ Target #[test] - fn inverter_uses_only_child_on_missing_ancestry() { + fn inverter_uses_only_child_on_missing_context() { parameter_types! { - pub Ancestry: MultiLocation = X1(PalletInstance(5)).into(); + pub UniversalLocation: InteriorMultiLocation = PalletInstance(5).into(); } let input = MultiLocation::grandparent(); - let inverted = LocationInverter::::invert_location(&input).unwrap(); - assert_eq!(inverted, X2(PalletInstance(5), OnlyChild).into()); + let inverted = UniversalLocation::get().invert_target(&input).unwrap(); + assert_eq!(inverted, (OnlyChild, PalletInstance(5)).into()); } #[test] fn inverter_errors_when_location_is_too_large() { parameter_types! { - pub Ancestry: MultiLocation = Here.into(); + pub UniversalLocation: InteriorMultiLocation = Here; } let input = MultiLocation { parents: 99, interior: X1(Parachain(88)) }; - let inverted = LocationInverter::::invert_location(&input); + let inverted = UniversalLocation::get().invert_target(&input); assert_eq!(inverted, Err(())); } } diff --git a/xcm/xcm-builder/src/matches_fungible.rs b/xcm/xcm-builder/src/matches_token.rs similarity index 65% rename from xcm/xcm-builder/src/matches_fungible.rs rename to xcm/xcm-builder/src/matches_token.rs index e4092500be02..53b844c75764 100644 --- a/xcm/xcm-builder/src/matches_fungible.rs +++ b/xcm/xcm-builder/src/matches_token.rs @@ -17,14 +17,14 @@ //! Various implementations for the `MatchesFungible` trait. use frame_support::traits::Get; -use sp_runtime::traits::CheckedConversion; use sp_std::marker::PhantomData; use xcm::latest::{ AssetId::{Abstract, Concrete}, - Fungibility::Fungible, + AssetInstance, + Fungibility::{Fungible, NonFungible}, MultiAsset, MultiLocation, }; -use xcm_executor::traits::MatchesFungible; +use xcm_executor::traits::{MatchesFungible, MatchesNonFungible}; /// Converts a `MultiAsset` into balance `B` if it is a concrete fungible with an id equal to that /// given by `T`'s `Get`. @@ -51,7 +51,15 @@ impl, B: TryFrom> MatchesFungible for IsConcrete< fn matches_fungible(a: &MultiAsset) -> Option { match (&a.id, &a.fun) { (Concrete(ref id), Fungible(ref amount)) if id == &T::get() => - CheckedConversion::checked_from(*amount), + (*amount).try_into().ok(), + _ => None, + } + } +} +impl, I: TryFrom> MatchesNonFungible for IsConcrete { + fn matches_nonfungible(a: &MultiAsset) -> Option { + match (&a.id, &a.fun) { + (Concrete(id), NonFungible(instance)) if id == &T::get() => (*instance).try_into().ok(), _ => None, } } @@ -64,24 +72,37 @@ impl, B: TryFrom> MatchesFungible for IsConcrete< /// ``` /// use xcm::latest::prelude::*; /// use xcm_builder::IsAbstract; -/// use xcm_executor::traits::MatchesFungible; +/// use xcm_executor::traits::{MatchesFungible, MatchesNonFungible}; /// /// frame_support::parameter_types! { -/// pub TargetLocation: &'static [u8] = &[7u8]; +/// pub TargetLocation: [u8; 32] = [7u8; 32]; /// } /// /// # fn main() { -/// let asset = (vec![7u8], 999).into(); -/// // match `asset` if it is a concrete asset in `TargetLocation`. +/// let asset = ([7u8; 32], 999u128).into(); +/// // match `asset` if it is an abstract asset in `TargetLocation`. /// assert_eq!( as MatchesFungible>::matches_fungible(&asset), Some(999)); +/// let nft = ([7u8; 32], [42u8; 4]).into(); +/// assert_eq!( +/// as MatchesNonFungible<[u8; 4]>>::matches_nonfungible(&nft), +/// Some([42u8; 4]) +/// ); /// # } /// ``` pub struct IsAbstract(PhantomData); -impl, B: TryFrom> MatchesFungible for IsAbstract { +impl, B: TryFrom> MatchesFungible for IsAbstract { fn matches_fungible(a: &MultiAsset) -> Option { match (&a.id, &a.fun) { (Abstract(ref id), Fungible(ref amount)) if id == &T::get() => - CheckedConversion::checked_from(*amount), + (*amount).try_into().ok(), + _ => None, + } + } +} +impl, B: TryFrom> MatchesNonFungible for IsAbstract { + fn matches_nonfungible(a: &MultiAsset) -> Option { + match (&a.id, &a.fun) { + (Abstract(id), NonFungible(instance)) if id == &T::get() => (*instance).try_into().ok(), _ => None, } } diff --git a/xcm/xcm-builder/src/mock.rs b/xcm/xcm-builder/src/mock.rs deleted file mode 100644 index e085f22f4127..000000000000 --- a/xcm/xcm-builder/src/mock.rs +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot 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. - -// Polkadot 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 Polkadot. If not, see . - -use crate::{barriers::AllowSubscriptionsFrom, test_utils::*}; -pub use crate::{ - AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, - FixedRateOfFungible, FixedWeightBounds, LocationInverter, TakeWeightCredit, -}; -pub use frame_support::{ - dispatch::{ - DispatchError, DispatchInfo, DispatchResultWithPostInfo, Dispatchable, GetDispatchInfo, - Parameter, PostDispatchInfo, - }, - ensure, parameter_types, - sp_runtime::DispatchErrorWithPostInfo, - traits::{Contains, Get, IsInVec}, -}; -pub use parity_scale_codec::{Decode, Encode}; -pub use sp_std::{ - cell::RefCell, - collections::{btree_map::BTreeMap, btree_set::BTreeSet}, - fmt::Debug, - marker::PhantomData, -}; -pub use xcm::latest::{prelude::*, Weight}; -pub use xcm_executor::{ - traits::{ConvertOrigin, FilterAssetLocation, InvertLocation, OnResponse, TransactAsset}, - Assets, Config, -}; - -pub enum TestOrigin { - Root, - Relay, - Signed(u64), - Parachain(u32), -} - -/// A dummy call. -/// -/// Each item contains the amount of weight that it *wants* to consume as the first item, and the actual amount (if -/// different from the former) in the second option. -#[derive(Debug, Encode, Decode, Eq, PartialEq, Clone, Copy, scale_info::TypeInfo)] -pub enum TestCall { - OnlyRoot(Weight, Option), - OnlyParachain(Weight, Option, Option), - OnlySigned(Weight, Option, Option), - Any(Weight, Option), -} -impl Dispatchable for TestCall { - type RuntimeOrigin = TestOrigin; - type Config = (); - type Info = (); - type PostInfo = PostDispatchInfo; - fn dispatch(self, origin: Self::RuntimeOrigin) -> DispatchResultWithPostInfo { - let mut post_info = PostDispatchInfo::default(); - let maybe_actual = match self { - TestCall::OnlyRoot(_, maybe_actual) | - TestCall::OnlySigned(_, maybe_actual, _) | - TestCall::OnlyParachain(_, maybe_actual, _) | - TestCall::Any(_, maybe_actual) => maybe_actual, - }; - post_info.actual_weight = - maybe_actual.map(|x| frame_support::weights::Weight::from_ref_time(x)); - if match (&origin, &self) { - (TestOrigin::Parachain(i), TestCall::OnlyParachain(_, _, Some(j))) => i == j, - (TestOrigin::Signed(i), TestCall::OnlySigned(_, _, Some(j))) => i == j, - (TestOrigin::Root, TestCall::OnlyRoot(..)) | - (TestOrigin::Parachain(_), TestCall::OnlyParachain(_, _, None)) | - (TestOrigin::Signed(_), TestCall::OnlySigned(_, _, None)) | - (_, TestCall::Any(..)) => true, - _ => false, - } { - Ok(post_info) - } else { - Err(DispatchErrorWithPostInfo { error: DispatchError::BadOrigin, post_info }) - } - } -} - -impl GetDispatchInfo for TestCall { - fn get_dispatch_info(&self) -> DispatchInfo { - let weight = *match self { - TestCall::OnlyRoot(estimate, ..) | - TestCall::OnlyParachain(estimate, ..) | - TestCall::OnlySigned(estimate, ..) | - TestCall::Any(estimate, ..) => estimate, - }; - DispatchInfo { - weight: frame_support::weights::Weight::from_ref_time(weight), - ..Default::default() - } - } -} - -thread_local! { - pub static SENT_XCM: RefCell> = RefCell::new(Vec::new()); -} -pub fn sent_xcm() -> Vec<(MultiLocation, opaque::Xcm)> { - SENT_XCM.with(|q| (*q.borrow()).clone()) -} -pub struct TestSendXcm; -impl SendXcm for TestSendXcm { - fn send_xcm(dest: impl Into, msg: opaque::Xcm) -> SendResult { - SENT_XCM.with(|q| q.borrow_mut().push((dest.into(), msg))); - Ok(()) - } -} - -thread_local! { - pub static ASSETS: RefCell> = RefCell::new(BTreeMap::new()); -} -pub fn assets(who: u64) -> Vec { - ASSETS.with(|a| a.borrow().get(&who).map_or(vec![], |a| a.clone().into())) -} -pub fn add_asset>(who: u64, what: AssetArg) { - ASSETS.with(|a| a.borrow_mut().entry(who).or_insert(Assets::new()).subsume(what.into())); -} - -pub struct TestAssetTransactor; -impl TransactAsset for TestAssetTransactor { - fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> Result<(), XcmError> { - let who = to_account(who.clone()).map_err(|_| XcmError::LocationCannotHold)?; - add_asset(who, what.clone()); - Ok(()) - } - - fn withdraw_asset(what: &MultiAsset, who: &MultiLocation) -> Result { - let who = to_account(who.clone()).map_err(|_| XcmError::LocationCannotHold)?; - ASSETS.with(|a| { - a.borrow_mut() - .get_mut(&who) - .ok_or(XcmError::NotWithdrawable)? - .try_take(what.clone().into()) - .map_err(|_| XcmError::NotWithdrawable) - }) - } -} - -pub fn to_account(l: MultiLocation) -> Result { - Ok(match l { - // Siblings at 2000+id - MultiLocation { parents: 1, interior: X1(Parachain(id)) } => 2000 + id as u64, - // Accounts are their number - MultiLocation { parents: 0, interior: X1(AccountIndex64 { index, .. }) } => index, - // Children at 1000+id - MultiLocation { parents: 0, interior: X1(Parachain(id)) } => 1000 + id as u64, - // Self at 3000 - MultiLocation { parents: 0, interior: Here } => 3000, - // Parent at 3001 - MultiLocation { parents: 1, interior: Here } => 3001, - _ => return Err(l), - }) -} - -pub struct TestOriginConverter; -impl ConvertOrigin for TestOriginConverter { - fn convert_origin( - origin: impl Into, - kind: OriginKind, - ) -> Result { - use OriginKind::*; - match (kind, origin.into()) { - (Superuser, _) => Ok(TestOrigin::Root), - (SovereignAccount, l) => Ok(TestOrigin::Signed(to_account(l)?)), - (Native, MultiLocation { parents: 0, interior: X1(Parachain(id)) }) => - Ok(TestOrigin::Parachain(id)), - (Native, MultiLocation { parents: 1, interior: Here }) => Ok(TestOrigin::Relay), - (Native, MultiLocation { parents: 0, interior: X1(AccountIndex64 { index, .. }) }) => - Ok(TestOrigin::Signed(index)), - (_, origin) => Err(origin), - } - } -} - -thread_local! { - pub static IS_RESERVE: RefCell>> = RefCell::new(BTreeMap::new()); - pub static IS_TELEPORTER: RefCell>> = RefCell::new(BTreeMap::new()); -} -pub fn add_reserve(from: MultiLocation, asset: MultiAssetFilter) { - IS_RESERVE.with(|r| r.borrow_mut().entry(from).or_default().push(asset)); -} -#[allow(dead_code)] -pub fn add_teleporter(from: MultiLocation, asset: MultiAssetFilter) { - IS_TELEPORTER.with(|r| r.borrow_mut().entry(from).or_default().push(asset)); -} -pub struct TestIsReserve; -impl FilterAssetLocation for TestIsReserve { - fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { - IS_RESERVE - .with(|r| r.borrow().get(origin).map_or(false, |v| v.iter().any(|a| a.contains(asset)))) - } -} -pub struct TestIsTeleporter; -impl FilterAssetLocation for TestIsTeleporter { - fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { - IS_TELEPORTER - .with(|r| r.borrow().get(origin).map_or(false, |v| v.iter().any(|a| a.contains(asset)))) - } -} - -use xcm::latest::Response; -pub enum ResponseSlot { - Expecting(MultiLocation), - Received(Response), -} -thread_local! { - pub static QUERIES: RefCell> = RefCell::new(BTreeMap::new()); -} -pub struct TestResponseHandler; -impl OnResponse for TestResponseHandler { - fn expecting_response(origin: &MultiLocation, query_id: u64) -> bool { - QUERIES.with(|q| match q.borrow().get(&query_id) { - Some(ResponseSlot::Expecting(ref l)) => l == origin, - _ => false, - }) - } - fn on_response( - _origin: &MultiLocation, - query_id: u64, - response: xcm::latest::Response, - _max_weight: Weight, - ) -> Weight { - QUERIES.with(|q| { - q.borrow_mut().entry(query_id).and_modify(|v| { - if matches!(*v, ResponseSlot::Expecting(..)) { - *v = ResponseSlot::Received(response); - } - }); - }); - 10 - } -} -pub fn expect_response(query_id: u64, from: MultiLocation) { - QUERIES.with(|q| q.borrow_mut().insert(query_id, ResponseSlot::Expecting(from))); -} -pub fn response(query_id: u64) -> Option { - QUERIES.with(|q| { - q.borrow().get(&query_id).and_then(|v| match v { - ResponseSlot::Received(r) => Some(r.clone()), - _ => None, - }) - }) -} - -parameter_types! { - pub TestAncestry: MultiLocation = X1(Parachain(42)).into(); - pub UnitWeightCost: u64 = 10; -} -parameter_types! { - // Nothing is allowed to be paid/unpaid by default. - pub static AllowUnpaidFrom: Vec = vec![]; - pub static AllowPaidFrom: Vec = vec![]; - pub static AllowSubsFrom: Vec = vec![]; - // 1_000_000_000_000 => 1 unit of asset for 1 unit of Weight. - pub static WeightPrice: (AssetId, u128) = (From::from(Here), 1_000_000_000_000); - pub static MaxInstructions: u32 = 100; -} - -pub type TestBarrier = ( - TakeWeightCredit, - AllowKnownQueryResponses, - AllowTopLevelPaidExecutionFrom>, - AllowUnpaidExecutionFrom>, - AllowSubscriptionsFrom>, -); - -pub struct TestConfig; -impl Config for TestConfig { - type RuntimeCall = TestCall; - type XcmSender = TestSendXcm; - type AssetTransactor = TestAssetTransactor; - type OriginConverter = TestOriginConverter; - type IsReserve = TestIsReserve; - type IsTeleporter = TestIsTeleporter; - type LocationInverter = LocationInverter; - type Barrier = TestBarrier; - type Weigher = FixedWeightBounds; - type Trader = FixedRateOfFungible; - type ResponseHandler = TestResponseHandler; - type AssetTrap = TestAssetTrap; - type AssetClaims = TestAssetTrap; - type SubscriptionService = TestSubscriptionService; -} diff --git a/xcm/xcm-builder/src/nonfungibles_adapter.rs b/xcm/xcm-builder/src/nonfungibles_adapter.rs new file mode 100644 index 000000000000..2735a03ab6f3 --- /dev/null +++ b/xcm/xcm-builder/src/nonfungibles_adapter.rs @@ -0,0 +1,327 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! Adapters to work with `frame_support::traits::tokens::fungibles` through XCM. + +use crate::{AssetChecking, MintLocation}; +use frame_support::{ + ensure, + traits::{tokens::nonfungibles, Get}, +}; +use sp_std::{marker::PhantomData, prelude::*, result}; +use xcm::latest::prelude::*; +use xcm_executor::traits::{Convert, Error as MatchError, MatchesNonFungibles, TransactAsset}; + +pub struct NonFungiblesTransferAdapter( + PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>, +); +impl< + Assets: nonfungibles::Transfer, + Matcher: MatchesNonFungibles, + AccountIdConverter: Convert, + AccountId: Clone, // can't get away without it since Currency is generic over it. + > TransactAsset for NonFungiblesTransferAdapter +{ + fn transfer_asset( + what: &MultiAsset, + from: &MultiLocation, + to: &MultiLocation, + context: &XcmContext, + ) -> result::Result { + log::trace!( + target: "xcm::non_fungibles_adapter", + "transfer_asset what: {:?}, from: {:?}, to: {:?}, context: {:?}", + what, from, to, context, + ); + // Check we handle this asset. + let (class, instance) = Matcher::matches_nonfungibles(what)?; + let destination = AccountIdConverter::convert_ref(to) + .map_err(|()| MatchError::AccountIdConversionFailed)?; + Assets::transfer(&class, &instance, &destination) + .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; + Ok(what.clone().into()) + } +} + +pub struct NonFungiblesMutateAdapter< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, +>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>); + +impl< + Assets: nonfungibles::Mutate, + Matcher: MatchesNonFungibles, + AccountIdConverter: Convert, + AccountId: Clone + Eq, // can't get away without it since Currency is generic over it. + CheckAsset: AssetChecking, + CheckingAccount: Get>, + > + NonFungiblesMutateAdapter< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + > +{ + fn can_accrue_checked(class: Assets::CollectionId, instance: Assets::ItemId) -> XcmResult { + ensure!(Assets::owner(&class, &instance).is_none(), XcmError::NotDepositable); + Ok(()) + } + fn can_reduce_checked(class: Assets::CollectionId, instance: Assets::ItemId) -> XcmResult { + if let Some(checking_account) = CheckingAccount::get() { + // This is an asset whose teleports we track. + let owner = Assets::owner(&class, &instance); + ensure!(owner == Some(checking_account), XcmError::NotWithdrawable); + ensure!(Assets::can_transfer(&class, &instance), XcmError::NotWithdrawable); + } + Ok(()) + } + fn accrue_checked(class: Assets::CollectionId, instance: Assets::ItemId) { + if let Some(checking_account) = CheckingAccount::get() { + let ok = Assets::mint_into(&class, &instance, &checking_account).is_ok(); + debug_assert!(ok, "`mint_into` cannot generally fail; qed"); + } + } + fn reduce_checked(class: Assets::CollectionId, instance: Assets::ItemId) { + let ok = Assets::burn(&class, &instance, None).is_ok(); + debug_assert!(ok, "`can_check_in` must have returned `true` immediately prior; qed"); + } +} + +impl< + Assets: nonfungibles::Mutate, + Matcher: MatchesNonFungibles, + AccountIdConverter: Convert, + AccountId: Clone + Eq, // can't get away without it since Currency is generic over it. + CheckAsset: AssetChecking, + CheckingAccount: Get>, + > TransactAsset + for NonFungiblesMutateAdapter< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + > +{ + fn can_check_in(_origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { + log::trace!( + target: "xcm::fungibles_adapter", + "can_check_in origin: {:?}, what: {:?}, context: {:?}", + _origin, what, context, + ); + // Check we handle this asset. + let (class, instance) = Matcher::matches_nonfungibles(what)?; + match CheckAsset::asset_checking(&class) { + // We track this asset's teleports to ensure no more come in than have gone out. + Some(MintLocation::Local) => Self::can_reduce_checked(class, instance), + // We track this asset's teleports to ensure no more go out than have come in. + Some(MintLocation::NonLocal) => Self::can_accrue_checked(class, instance), + _ => Ok(()), + } + } + + fn check_in(_origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) { + log::trace!( + target: "xcm::fungibles_adapter", + "check_in origin: {:?}, what: {:?}, context: {:?}", + _origin, what, context, + ); + if let Ok((class, instance)) = Matcher::matches_nonfungibles(what) { + match CheckAsset::asset_checking(&class) { + // We track this asset's teleports to ensure no more come in than have gone out. + Some(MintLocation::Local) => Self::reduce_checked(class, instance), + // We track this asset's teleports to ensure no more go out than have come in. + Some(MintLocation::NonLocal) => Self::accrue_checked(class, instance), + _ => (), + } + } + } + + fn can_check_out(_dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { + log::trace!( + target: "xcm::fungibles_adapter", + "can_check_out dest: {:?}, what: {:?}, context: {:?}", + _dest, what, context, + ); + // Check we handle this asset. + let (class, instance) = Matcher::matches_nonfungibles(what)?; + match CheckAsset::asset_checking(&class) { + // We track this asset's teleports to ensure no more come in than have gone out. + Some(MintLocation::Local) => Self::can_accrue_checked(class, instance), + // We track this asset's teleports to ensure no more go out than have come in. + Some(MintLocation::NonLocal) => Self::can_reduce_checked(class, instance), + _ => Ok(()), + } + } + + fn check_out(_dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) { + log::trace!( + target: "xcm::fungibles_adapter", + "check_out dest: {:?}, what: {:?}, context: {:?}", + _dest, what, context, + ); + if let Ok((class, instance)) = Matcher::matches_nonfungibles(what) { + match CheckAsset::asset_checking(&class) { + // We track this asset's teleports to ensure no more come in than have gone out. + Some(MintLocation::Local) => Self::accrue_checked(class, instance), + // We track this asset's teleports to ensure no more go out than have come in. + Some(MintLocation::NonLocal) => Self::reduce_checked(class, instance), + _ => (), + } + } + } + + fn deposit_asset(what: &MultiAsset, who: &MultiLocation, context: &XcmContext) -> XcmResult { + log::trace!( + target: "xcm::fungibles_adapter", + "deposit_asset what: {:?}, who: {:?}, context: {:?}", + what, who, context, + ); + // Check we handle this asset. + let (class, instance) = Matcher::matches_nonfungibles(what)?; + let who = AccountIdConverter::convert_ref(who) + .map_err(|()| MatchError::AccountIdConversionFailed)?; + Assets::mint_into(&class, &instance, &who) + .map_err(|e| XcmError::FailedToTransactAsset(e.into())) + } + + fn withdraw_asset( + what: &MultiAsset, + who: &MultiLocation, + maybe_context: Option<&XcmContext>, + ) -> result::Result { + log::trace!( + target: "xcm::fungibles_adapter", + "withdraw_asset what: {:?}, who: {:?}, maybe_context: {:?}", + what, who, maybe_context, + ); + // Check we handle this asset. + let who = AccountIdConverter::convert_ref(who) + .map_err(|()| MatchError::AccountIdConversionFailed)?; + let (class, instance) = Matcher::matches_nonfungibles(what)?; + Assets::burn(&class, &instance, Some(&who)) + .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; + Ok(what.clone().into()) + } +} + +pub struct NonFungiblesAdapter< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, +>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>); +impl< + Assets: nonfungibles::Mutate + nonfungibles::Transfer, + Matcher: MatchesNonFungibles, + AccountIdConverter: Convert, + AccountId: Clone + Eq, // can't get away without it since Currency is generic over it. + CheckAsset: AssetChecking, + CheckingAccount: Get>, + > TransactAsset + for NonFungiblesAdapter +{ + fn can_check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { + NonFungiblesMutateAdapter::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + >::can_check_in(origin, what, context) + } + + fn check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) { + NonFungiblesMutateAdapter::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + >::check_in(origin, what, context) + } + + fn can_check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { + NonFungiblesMutateAdapter::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + >::can_check_out(dest, what, context) + } + + fn check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) { + NonFungiblesMutateAdapter::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + >::check_out(dest, what, context) + } + + fn deposit_asset(what: &MultiAsset, who: &MultiLocation, context: &XcmContext) -> XcmResult { + NonFungiblesMutateAdapter::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + >::deposit_asset(what, who, context) + } + + fn withdraw_asset( + what: &MultiAsset, + who: &MultiLocation, + maybe_context: Option<&XcmContext>, + ) -> result::Result { + NonFungiblesMutateAdapter::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + >::withdraw_asset(what, who, maybe_context) + } + + fn transfer_asset( + what: &MultiAsset, + from: &MultiLocation, + to: &MultiLocation, + context: &XcmContext, + ) -> result::Result { + NonFungiblesTransferAdapter::::transfer_asset( + what, from, to, context, + ) + } +} diff --git a/xcm/xcm-builder/src/origin_conversion.rs b/xcm/xcm-builder/src/origin_conversion.rs index 4d024a9d12c2..ddeec5dd0b31 100644 --- a/xcm/xcm-builder/src/origin_conversion.rs +++ b/xcm/xcm-builder/src/origin_conversion.rs @@ -187,7 +187,7 @@ impl, RuntimeOrigin> ConvertOrigin(PhantomData<(Network, RuntimeOrigin)>); -impl, RuntimeOrigin: OriginTrait> ConvertOrigin +impl>, RuntimeOrigin: OriginTrait> ConvertOrigin for SignedAccountId32AsNative where RuntimeOrigin::AccountId: From<[u8; 32]>, @@ -206,7 +206,7 @@ where ( OriginKind::Native, MultiLocation { parents: 0, interior: X1(Junction::AccountId32 { id, network }) }, - ) if matches!(network, NetworkId::Any) || network == Network::get() => + ) if matches!(network, None) || network == Network::get() => Ok(RuntimeOrigin::signed(id.into())), (_, origin) => Err(origin), } @@ -216,7 +216,7 @@ where pub struct SignedAccountKey20AsNative( PhantomData<(Network, RuntimeOrigin)>, ); -impl, RuntimeOrigin: OriginTrait> ConvertOrigin +impl>, RuntimeOrigin: OriginTrait> ConvertOrigin for SignedAccountKey20AsNative where RuntimeOrigin::AccountId: From<[u8; 20]>, @@ -235,7 +235,7 @@ where ( OriginKind::Native, MultiLocation { parents: 0, interior: X1(Junction::AccountKey20 { key, network }) }, - ) if (matches!(network, NetworkId::Any) || network == Network::get()) => + ) if (matches!(network, None) || network == Network::get()) => Ok(RuntimeOrigin::signed(key.into())), (_, origin) => Err(origin), } @@ -277,8 +277,11 @@ where pub struct SignedToAccountId32( PhantomData<(RuntimeOrigin, AccountId, Network)>, ); -impl, Network: Get> - Convert for SignedToAccountId32 +impl< + RuntimeOrigin: OriginTrait + Clone, + AccountId: Into<[u8; 32]>, + Network: Get>, + > Convert for SignedToAccountId32 where RuntimeOrigin::PalletsOrigin: From> + TryInto, Error = RuntimeOrigin::PalletsOrigin>, diff --git a/xcm/xcm-builder/src/test_utils.rs b/xcm/xcm-builder/src/test_utils.rs index e233390beccd..958d6a8a4a3a 100644 --- a/xcm/xcm-builder/src/test_utils.rs +++ b/xcm/xcm-builder/src/test_utils.rs @@ -16,32 +16,43 @@ // Shared test utilities and implementations for the XCM Builder. -use frame_support::parameter_types; +use frame_support::{ + parameter_types, + traits::{Contains, CrateVersion, PalletInfoData, PalletsInfoAccess}, +}; use sp_std::vec::Vec; pub use xcm::latest::{prelude::*, Weight}; use xcm_executor::traits::{ClaimAssets, DropAssets, VersionChangeNotifier}; pub use xcm_executor::{ - traits::{ConvertOrigin, FilterAssetLocation, InvertLocation, OnResponse, TransactAsset}, + traits::{ + AssetExchange, AssetLock, ConvertOrigin, Enact, LockError, OnResponse, TransactAsset, + }, Assets, Config, }; parameter_types! { - pub static SubscriptionRequests: Vec<(MultiLocation, Option<(QueryId, u64)>)> = vec![]; + pub static SubscriptionRequests: Vec<(MultiLocation, Option<(QueryId, Weight)>)> = vec![]; + pub static MaxAssetsIntoHolding: u32 = 4; } pub struct TestSubscriptionService; impl VersionChangeNotifier for TestSubscriptionService { - fn start(location: &MultiLocation, query_id: QueryId, max_weight: u64) -> XcmResult { + fn start( + location: &MultiLocation, + query_id: QueryId, + max_weight: Weight, + _context: &XcmContext, + ) -> XcmResult { let mut r = SubscriptionRequests::get(); - r.push((location.clone(), Some((query_id, max_weight)))); + r.push((*location, Some((query_id, max_weight)))); SubscriptionRequests::set(r); Ok(()) } - fn stop(location: &MultiLocation) -> XcmResult { + fn stop(location: &MultiLocation, _context: &XcmContext) -> XcmResult { let mut r = SubscriptionRequests::get(); r.retain(|(l, _q)| l != location); - r.push((location.clone(), None)); + r.push((*location, None)); SubscriptionRequests::set(r); Ok(()) } @@ -58,16 +69,21 @@ parameter_types! { pub struct TestAssetTrap; impl DropAssets for TestAssetTrap { - fn drop_assets(origin: &MultiLocation, assets: Assets) -> Weight { + fn drop_assets(origin: &MultiLocation, assets: Assets, _context: &XcmContext) -> Weight { let mut t: Vec<(MultiLocation, MultiAssets)> = TrappedAssets::get(); - t.push((origin.clone(), assets.into())); + t.push((*origin, assets.into())); TrappedAssets::set(t); - 5 + Weight::from_parts(5, 5) } } impl ClaimAssets for TestAssetTrap { - fn claim_assets(origin: &MultiLocation, ticket: &MultiLocation, what: &MultiAssets) -> bool { + fn claim_assets( + origin: &MultiLocation, + ticket: &MultiLocation, + what: &MultiAssets, + _context: &XcmContext, + ) -> bool { let mut t: Vec<(MultiLocation, MultiAssets)> = TrappedAssets::get(); if let (0, X1(GeneralIndex(i))) = (ticket.parents, &ticket.interior) { if let Some((l, a)) = t.get(*i as usize) { @@ -81,3 +97,120 @@ impl ClaimAssets for TestAssetTrap { false } } + +pub struct TestAssetExchanger; + +impl AssetExchange for TestAssetExchanger { + fn exchange_asset( + _origin: Option<&MultiLocation>, + _give: Assets, + want: &MultiAssets, + _maximal: bool, + ) -> Result { + Ok(want.clone().into()) + } +} + +pub struct TestPalletsInfo; +impl PalletsInfoAccess for TestPalletsInfo { + fn count() -> usize { + 2 + } + fn infos() -> Vec { + vec![ + PalletInfoData { + index: 0, + name: "System", + module_name: "pallet_system", + crate_version: CrateVersion { major: 1, minor: 10, patch: 1 }, + }, + PalletInfoData { + index: 1, + name: "Balances", + module_name: "pallet_balances", + crate_version: CrateVersion { major: 1, minor: 42, patch: 69 }, + }, + ] + } +} + +pub struct TestUniversalAliases; +impl Contains<(MultiLocation, Junction)> for TestUniversalAliases { + fn contains(aliases: &(MultiLocation, Junction)) -> bool { + &aliases.0 == &Here.into_location() && &aliases.1 == &GlobalConsensus(ByGenesis([0; 32])) + } +} + +parameter_types! { + pub static LockedAssets: Vec<(MultiLocation, MultiAsset)> = vec![]; +} + +pub struct TestLockTicket(MultiLocation, MultiAsset); +impl Enact for TestLockTicket { + fn enact(self) -> Result<(), LockError> { + let mut locked_assets = LockedAssets::get(); + locked_assets.push((self.0, self.1)); + LockedAssets::set(locked_assets); + Ok(()) + } +} +pub struct TestUnlockTicket(MultiLocation, MultiAsset); +impl Enact for TestUnlockTicket { + fn enact(self) -> Result<(), LockError> { + let mut locked_assets = LockedAssets::get(); + if let Some((idx, _)) = locked_assets + .iter() + .enumerate() + .find(|(_, (origin, asset))| origin == &self.0 && asset == &self.1) + { + locked_assets.remove(idx); + } + LockedAssets::set(locked_assets); + Ok(()) + } +} +pub struct TestReduceTicket; +impl Enact for TestReduceTicket { + fn enact(self) -> Result<(), LockError> { + Ok(()) + } +} + +pub struct TestAssetLocker; +impl AssetLock for TestAssetLocker { + type LockTicket = TestLockTicket; + type UnlockTicket = TestUnlockTicket; + type ReduceTicket = TestReduceTicket; + + fn prepare_lock( + unlocker: MultiLocation, + asset: MultiAsset, + _owner: MultiLocation, + ) -> Result { + Ok(TestLockTicket(unlocker, asset)) + } + + fn prepare_unlock( + unlocker: MultiLocation, + asset: MultiAsset, + _owner: MultiLocation, + ) -> Result { + Ok(TestUnlockTicket(unlocker, asset)) + } + + fn note_unlockable( + _locker: MultiLocation, + _asset: MultiAsset, + _owner: MultiLocation, + ) -> Result<(), LockError> { + Ok(()) + } + + fn prepare_reduce_unlockable( + _locker: MultiLocation, + _asset: MultiAsset, + _owner: MultiLocation, + ) -> Result { + Ok(TestReduceTicket) + } +} diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs deleted file mode 100644 index d26a2f008884..000000000000 --- a/xcm/xcm-builder/src/tests.rs +++ /dev/null @@ -1,716 +0,0 @@ -// Copyright 2020 Parity Technologies query_id: (), max_response_weight: () query_id: (), max_response_weight: () (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot 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. - -// Polkadot 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 Polkadot. If not, see . - -use super::{mock::*, test_utils::*, *}; -use frame_support::{assert_err, weights::constants::WEIGHT_REF_TIME_PER_SECOND}; -use xcm::latest::prelude::*; -use xcm_executor::{traits::*, Config, XcmExecutor}; - -#[test] -fn basic_setup_works() { - add_reserve(Parent.into(), Wild((Parent, WildFungible).into())); - assert!(::IsReserve::filter_asset_location( - &(Parent, 100).into(), - &Parent.into(), - )); - - assert_eq!(to_account(X1(Parachain(1)).into()), Ok(1001)); - assert_eq!(to_account(X1(Parachain(50)).into()), Ok(1050)); - assert_eq!(to_account(MultiLocation::new(1, X1(Parachain(1)))), Ok(2001)); - assert_eq!(to_account(MultiLocation::new(1, X1(Parachain(50)))), Ok(2050)); - assert_eq!( - to_account(MultiLocation::new(0, X1(AccountIndex64 { index: 1, network: Any }))), - Ok(1), - ); - assert_eq!( - to_account(MultiLocation::new(0, X1(AccountIndex64 { index: 42, network: Any }))), - Ok(42), - ); - assert_eq!(to_account(Here.into()), Ok(3000)); -} - -#[test] -fn weigher_should_work() { - let mut message = Xcm(vec![ - ReserveAssetDeposited((Parent, 100).into()), - BuyExecution { fees: (Parent, 1).into(), weight_limit: Limited(30) }, - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, - ]); - assert_eq!(::Weigher::weight(&mut message), Ok(30)); -} - -#[test] -fn take_weight_credit_barrier_should_work() { - let mut message = - Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]); - let mut weight_credit = 10; - let r = TakeWeightCredit::should_execute(&Parent.into(), &mut message, 10, &mut weight_credit); - assert_eq!(r, Ok(())); - assert_eq!(weight_credit, 0); - - let r = TakeWeightCredit::should_execute(&Parent.into(), &mut message, 10, &mut weight_credit); - assert_eq!(r, Err(())); - assert_eq!(weight_credit, 0); -} - -#[test] -fn allow_unpaid_should_work() { - let mut message = - Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]); - - AllowUnpaidFrom::set(vec![Parent.into()]); - - let r = AllowUnpaidExecutionFrom::>::should_execute( - &Parachain(1).into(), - &mut message, - 10, - &mut 0, - ); - assert_eq!(r, Err(())); - - let r = AllowUnpaidExecutionFrom::>::should_execute( - &Parent.into(), - &mut message, - 10, - &mut 0, - ); - assert_eq!(r, Ok(())); -} - -#[test] -fn allow_paid_should_work() { - AllowPaidFrom::set(vec![Parent.into()]); - - let mut message = - Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]); - - let r = AllowTopLevelPaidExecutionFrom::>::should_execute( - &Parachain(1).into(), - &mut message, - 10, - &mut 0, - ); - assert_eq!(r, Err(())); - - let fees = (Parent, 1).into(); - let mut underpaying_message = Xcm::<()>(vec![ - ReserveAssetDeposited((Parent, 100).into()), - BuyExecution { fees, weight_limit: Limited(20) }, - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, - ]); - - let r = AllowTopLevelPaidExecutionFrom::>::should_execute( - &Parent.into(), - &mut underpaying_message, - 30, - &mut 0, - ); - assert_eq!(r, Err(())); - - let fees = (Parent, 1).into(); - let mut paying_message = Xcm::<()>(vec![ - ReserveAssetDeposited((Parent, 100).into()), - BuyExecution { fees, weight_limit: Limited(30) }, - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, - ]); - - let r = AllowTopLevelPaidExecutionFrom::>::should_execute( - &Parachain(1).into(), - &mut paying_message, - 30, - &mut 0, - ); - assert_eq!(r, Err(())); - - let r = AllowTopLevelPaidExecutionFrom::>::should_execute( - &Parent.into(), - &mut paying_message, - 30, - &mut 0, - ); - assert_eq!(r, Ok(())); -} - -#[test] -fn paying_reserve_deposit_should_work() { - AllowPaidFrom::set(vec![Parent.into()]); - add_reserve(Parent.into(), (Parent, WildFungible).into()); - WeightPrice::set((Parent.into(), 1_000_000_000_000)); - - let fees = (Parent, 30).into(); - let message = Xcm(vec![ - ReserveAssetDeposited((Parent, 100).into()), - BuyExecution { fees, weight_limit: Limited(30) }, - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, - ]); - let weight_limit = 50; - let r = XcmExecutor::::execute_xcm(Parent, message, weight_limit); - assert_eq!(r, Outcome::Complete(30)); - assert_eq!(assets(3000), vec![(Parent, 70).into()]); -} - -#[test] -fn transfer_should_work() { - // we'll let them have message execution for free. - AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); - // Child parachain #1 owns 1000 tokens held by us in reserve. - add_asset(1001, (Here, 1000)); - // They want to transfer 100 of them to their sibling parachain #2 - let r = XcmExecutor::::execute_xcm( - Parachain(1), - Xcm(vec![TransferAsset { - assets: (Here, 100).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), - }]), - 50, - ); - assert_eq!(r, Outcome::Complete(10)); - assert_eq!(assets(3), vec![(Here, 100).into()]); - assert_eq!(assets(1001), vec![(Here, 900).into()]); - assert_eq!(sent_xcm(), vec![]); -} - -#[test] -fn basic_asset_trap_should_work() { - // we'll let them have message execution for free. - AllowUnpaidFrom::set(vec![X1(Parachain(1)).into(), X1(Parachain(2)).into()]); - - // Child parachain #1 owns 1000 tokens held by us in reserve. - add_asset(1001, (Here, 1000)); - // They want to transfer 100 of them to their sibling parachain #2 but have a problem - let r = XcmExecutor::::execute_xcm( - Parachain(1).into(), - Xcm(vec![ - WithdrawAsset((Here, 100).into()), - DepositAsset { - assets: Wild(All), - max_assets: 0, //< Whoops! - beneficiary: AccountIndex64 { index: 3, network: Any }.into(), - }, - ]), - 20, - ); - assert_eq!(r, Outcome::Complete(25)); - assert_eq!(assets(1001), vec![(Here, 900).into()]); - assert_eq!(assets(3), vec![]); - - // Incorrect ticket doesn't work. - let old_trapped_assets = TrappedAssets::get(); - let r = XcmExecutor::::execute_xcm( - Parachain(1).into(), - Xcm(vec![ - ClaimAsset { assets: (Here, 100).into(), ticket: GeneralIndex(1).into() }, - DepositAsset { - assets: Wild(All), - max_assets: 1, - beneficiary: AccountIndex64 { index: 3, network: Any }.into(), - }, - ]), - 20, - ); - assert_eq!(r, Outcome::Incomplete(10, XcmError::UnknownClaim)); - assert_eq!(assets(1001), vec![(Here, 900).into()]); - assert_eq!(assets(3), vec![]); - assert_eq!(old_trapped_assets, TrappedAssets::get()); - - // Incorrect origin doesn't work. - let old_trapped_assets = TrappedAssets::get(); - let r = XcmExecutor::::execute_xcm( - Parachain(2).into(), - Xcm(vec![ - ClaimAsset { assets: (Here, 100).into(), ticket: GeneralIndex(0).into() }, - DepositAsset { - assets: Wild(All), - max_assets: 1, - beneficiary: AccountIndex64 { index: 3, network: Any }.into(), - }, - ]), - 20, - ); - assert_eq!(r, Outcome::Incomplete(10, XcmError::UnknownClaim)); - assert_eq!(assets(1001), vec![(Here, 900).into()]); - assert_eq!(assets(3), vec![]); - assert_eq!(old_trapped_assets, TrappedAssets::get()); - - // Incorrect assets doesn't work. - let old_trapped_assets = TrappedAssets::get(); - let r = XcmExecutor::::execute_xcm( - Parachain(1).into(), - Xcm(vec![ - ClaimAsset { assets: (Here, 101).into(), ticket: GeneralIndex(0).into() }, - DepositAsset { - assets: Wild(All), - max_assets: 1, - beneficiary: AccountIndex64 { index: 3, network: Any }.into(), - }, - ]), - 20, - ); - assert_eq!(r, Outcome::Incomplete(10, XcmError::UnknownClaim)); - assert_eq!(assets(1001), vec![(Here, 900).into()]); - assert_eq!(assets(3), vec![]); - assert_eq!(old_trapped_assets, TrappedAssets::get()); - - let r = XcmExecutor::::execute_xcm( - Parachain(1).into(), - Xcm(vec![ - ClaimAsset { assets: (Here, 100).into(), ticket: GeneralIndex(0).into() }, - DepositAsset { - assets: Wild(All), - max_assets: 1, - beneficiary: AccountIndex64 { index: 3, network: Any }.into(), - }, - ]), - 20, - ); - assert_eq!(r, Outcome::Complete(20)); - assert_eq!(assets(1001), vec![(Here, 900).into()]); - assert_eq!(assets(3), vec![(Here, 100).into()]); - - // Same again doesn't work :-) - let r = XcmExecutor::::execute_xcm( - Parachain(1).into(), - Xcm(vec![ - ClaimAsset { assets: (Here, 100).into(), ticket: GeneralIndex(0).into() }, - DepositAsset { - assets: Wild(All), - max_assets: 1, - beneficiary: AccountIndex64 { index: 3, network: Any }.into(), - }, - ]), - 20, - ); - assert_eq!(r, Outcome::Incomplete(10, XcmError::UnknownClaim)); -} - -#[test] -fn errors_should_return_unused_weight() { - // we'll let them have message execution for free. - AllowUnpaidFrom::set(vec![Here.into()]); - // We own 1000 of our tokens. - add_asset(3000, (Here, 11)); - let mut message = Xcm(vec![ - // First xfer results in an error on the last message only - TransferAsset { - assets: (Here, 1).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), - }, - // Second xfer results in error third message and after - TransferAsset { - assets: (Here, 2).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), - }, - // Third xfer results in error second message and after - TransferAsset { - assets: (Here, 4).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), - }, - ]); - // Weight limit of 70 is needed. - let limit = ::Weigher::weight(&mut message).unwrap(); - assert_eq!(limit, 30); - - let r = XcmExecutor::::execute_xcm(Here.into(), message.clone(), limit); - assert_eq!(r, Outcome::Complete(30)); - assert_eq!(assets(3), vec![(Here, 7).into()]); - assert_eq!(assets(3000), vec![(Here, 4).into()]); - assert_eq!(sent_xcm(), vec![]); - - let r = XcmExecutor::::execute_xcm(Here.into(), message.clone(), limit); - assert_eq!(r, Outcome::Incomplete(30, XcmError::NotWithdrawable)); - assert_eq!(assets(3), vec![(Here, 10).into()]); - assert_eq!(assets(3000), vec![(Here, 1).into()]); - assert_eq!(sent_xcm(), vec![]); - - let r = XcmExecutor::::execute_xcm(Here.into(), message.clone(), limit); - assert_eq!(r, Outcome::Incomplete(20, XcmError::NotWithdrawable)); - assert_eq!(assets(3), vec![(Here, 11).into()]); - assert_eq!(assets(3000), vec![]); - assert_eq!(sent_xcm(), vec![]); - - let r = XcmExecutor::::execute_xcm(Here.into(), message, limit); - assert_eq!(r, Outcome::Incomplete(10, XcmError::NotWithdrawable)); - assert_eq!(assets(3), vec![(Here, 11).into()]); - assert_eq!(assets(3000), vec![]); - assert_eq!(sent_xcm(), vec![]); -} - -#[test] -fn weight_bounds_should_respect_instructions_limit() { - MaxInstructions::set(3); - let mut message = Xcm(vec![ClearOrigin; 4]); - // 4 instructions are too many. - assert_eq!(::Weigher::weight(&mut message), Err(())); - - let mut message = - Xcm(vec![SetErrorHandler(Xcm(vec![ClearOrigin])), SetAppendix(Xcm(vec![ClearOrigin]))]); - // 4 instructions are too many, even when hidden within 2. - assert_eq!(::Weigher::weight(&mut message), Err(())); - - let mut message = - Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm( - vec![ClearOrigin], - ))]))]))]); - // 4 instructions are too many, even when it's just one that's 3 levels deep. - assert_eq!(::Weigher::weight(&mut message), Err(())); - - let mut message = - Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm(vec![ClearOrigin]))]))]); - // 3 instructions are OK. - assert_eq!(::Weigher::weight(&mut message), Ok(30)); -} - -#[test] -fn code_registers_should_work() { - // we'll let them have message execution for free. - AllowUnpaidFrom::set(vec![Here.into()]); - // We own 1000 of our tokens. - add_asset(3000, (Here, 21)); - let mut message = Xcm(vec![ - // Set our error handler - this will fire only on the second message, when there's an error - SetErrorHandler(Xcm(vec![ - TransferAsset { - assets: (Here, 2).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), - }, - // It was handled fine. - ClearError, - ])), - // Set the appendix - this will always fire. - SetAppendix(Xcm(vec![TransferAsset { - assets: (Here, 4).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), - }])), - // First xfer always works ok - TransferAsset { - assets: (Here, 1).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), - }, - // Second xfer results in error on the second message - our error handler will fire. - TransferAsset { - assets: (Here, 8).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), - }, - ]); - // Weight limit of 70 is needed. - let limit = ::Weigher::weight(&mut message).unwrap(); - assert_eq!(limit, 70); - - let r = XcmExecutor::::execute_xcm(Here.into(), message.clone(), limit); - assert_eq!(r, Outcome::Complete(50)); // We don't pay the 20 weight for the error handler. - assert_eq!(assets(3), vec![(Here, 13).into()]); - assert_eq!(assets(3000), vec![(Here, 8).into()]); - assert_eq!(sent_xcm(), vec![]); - - let r = XcmExecutor::::execute_xcm(Here.into(), message, limit); - assert_eq!(r, Outcome::Complete(70)); // We pay the full weight here. - assert_eq!(assets(3), vec![(Here, 20).into()]); - assert_eq!(assets(3000), vec![(Here, 1).into()]); - assert_eq!(sent_xcm(), vec![]); -} - -#[test] -fn reserve_transfer_should_work() { - AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); - // Child parachain #1 owns 1000 tokens held by us in reserve. - add_asset(1001, (Here, 1000)); - // The remote account owned by gav. - let three: MultiLocation = X1(AccountIndex64 { index: 3, network: Any }).into(); - - // They want to transfer 100 of our native asset from sovereign account of parachain #1 into #2 - // and let them know to hand it to account #3. - let r = XcmExecutor::::execute_xcm( - Parachain(1), - Xcm(vec![TransferReserveAsset { - assets: (Here, 100).into(), - dest: Parachain(2).into(), - xcm: Xcm::<()>(vec![DepositAsset { - assets: All.into(), - max_assets: 1, - beneficiary: three.clone(), - }]), - }]), - 50, - ); - assert_eq!(r, Outcome::Complete(10)); - - assert_eq!(assets(1002), vec![(Here, 100).into()]); - assert_eq!( - sent_xcm(), - vec![( - Parachain(2).into(), - Xcm::<()>(vec![ - ReserveAssetDeposited((Parent, 100).into()), - ClearOrigin, - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: three }, - ]), - )] - ); -} - -#[test] -fn simple_version_subscriptions_should_work() { - AllowSubsFrom::set(vec![Parent.into()]); - - let origin = Parachain(1000).into(); - let message = Xcm::(vec![ - SetAppendix(Xcm(vec![])), - SubscribeVersion { query_id: 42, max_response_weight: 5000 }, - ]); - let weight_limit = 20; - let r = XcmExecutor::::execute_xcm(origin, message, weight_limit); - assert_eq!(r, Outcome::Error(XcmError::Barrier)); - - let origin = Parachain(1000).into(); - let message = - Xcm::(vec![SubscribeVersion { query_id: 42, max_response_weight: 5000 }]); - let weight_limit = 10; - let r = XcmExecutor::::execute_xcm(origin, message.clone(), weight_limit); - assert_eq!(r, Outcome::Error(XcmError::Barrier)); - - let r = XcmExecutor::::execute_xcm(Parent, message, weight_limit); - assert_eq!(r, Outcome::Complete(10)); - - assert_eq!(SubscriptionRequests::get(), vec![(Parent.into(), Some((42, 5000)))]); -} - -#[test] -fn version_subscription_instruction_should_work() { - let origin = Parachain(1000).into(); - let message = Xcm::(vec![ - DescendOrigin(X1(AccountIndex64 { index: 1, network: Any })), - SubscribeVersion { query_id: 42, max_response_weight: 5000 }, - ]); - let weight_limit = 20; - let r = XcmExecutor::::execute_xcm_in_credit( - origin.clone(), - message.clone(), - weight_limit, - weight_limit, - ); - assert_eq!(r, Outcome::Incomplete(20, XcmError::BadOrigin)); - - let message = Xcm::(vec![ - SetAppendix(Xcm(vec![])), - SubscribeVersion { query_id: 42, max_response_weight: 5000 }, - ]); - let r = XcmExecutor::::execute_xcm_in_credit( - origin, - message.clone(), - weight_limit, - weight_limit, - ); - assert_eq!(r, Outcome::Complete(20)); - - assert_eq!(SubscriptionRequests::get(), vec![(Parachain(1000).into(), Some((42, 5000)))]); -} - -#[test] -fn simple_version_unsubscriptions_should_work() { - AllowSubsFrom::set(vec![Parent.into()]); - - let origin = Parachain(1000).into(); - let message = Xcm::(vec![SetAppendix(Xcm(vec![])), UnsubscribeVersion]); - let weight_limit = 20; - let r = XcmExecutor::::execute_xcm(origin, message, weight_limit); - assert_eq!(r, Outcome::Error(XcmError::Barrier)); - - let origin = Parachain(1000).into(); - let message = Xcm::(vec![UnsubscribeVersion]); - let weight_limit = 10; - let r = XcmExecutor::::execute_xcm(origin, message.clone(), weight_limit); - assert_eq!(r, Outcome::Error(XcmError::Barrier)); - - let r = XcmExecutor::::execute_xcm(Parent, message, weight_limit); - assert_eq!(r, Outcome::Complete(10)); - - assert_eq!(SubscriptionRequests::get(), vec![(Parent.into(), None)]); - assert_eq!(sent_xcm(), vec![]); -} - -#[test] -fn version_unsubscription_instruction_should_work() { - let origin = Parachain(1000).into(); - - // Not allowed to do it when origin has been changed. - let message = Xcm::(vec![ - DescendOrigin(X1(AccountIndex64 { index: 1, network: Any })), - UnsubscribeVersion, - ]); - let weight_limit = 20; - let r = XcmExecutor::::execute_xcm_in_credit( - origin.clone(), - message.clone(), - weight_limit, - weight_limit, - ); - assert_eq!(r, Outcome::Incomplete(20, XcmError::BadOrigin)); - - // Fine to do it when origin is untouched. - let message = Xcm::(vec![SetAppendix(Xcm(vec![])), UnsubscribeVersion]); - let r = XcmExecutor::::execute_xcm_in_credit( - origin, - message.clone(), - weight_limit, - weight_limit, - ); - assert_eq!(r, Outcome::Complete(20)); - - assert_eq!(SubscriptionRequests::get(), vec![(Parachain(1000).into(), None)]); - assert_eq!(sent_xcm(), vec![]); -} - -#[test] -fn transacting_should_work() { - AllowUnpaidFrom::set(vec![Parent.into()]); - - let message = Xcm::(vec![Transact { - origin_type: OriginKind::Native, - require_weight_at_most: 50, - call: TestCall::Any(50, None).encode().into(), - }]); - let weight_limit = 60; - let r = XcmExecutor::::execute_xcm(Parent, message, weight_limit); - assert_eq!(r, Outcome::Complete(60)); -} - -#[test] -fn transacting_should_respect_max_weight_requirement() { - AllowUnpaidFrom::set(vec![Parent.into()]); - - let message = Xcm::(vec![Transact { - origin_type: OriginKind::Native, - require_weight_at_most: 40, - call: TestCall::Any(50, None).encode().into(), - }]); - let weight_limit = 60; - let r = XcmExecutor::::execute_xcm(Parent, message, weight_limit); - assert_eq!(r, Outcome::Incomplete(50, XcmError::MaxWeightInvalid)); -} - -#[test] -fn transacting_should_refund_weight() { - AllowUnpaidFrom::set(vec![Parent.into()]); - - let message = Xcm::(vec![Transact { - origin_type: OriginKind::Native, - require_weight_at_most: 50, - call: TestCall::Any(50, Some(30)).encode().into(), - }]); - let weight_limit = 60; - let r = XcmExecutor::::execute_xcm(Parent, message, weight_limit); - assert_eq!(r, Outcome::Complete(40)); -} - -#[test] -fn paid_transacting_should_refund_payment_for_unused_weight() { - let one: MultiLocation = X1(AccountIndex64 { index: 1, network: Any }).into(); - AllowPaidFrom::set(vec![one.clone()]); - add_asset(1, (Parent, 100)); - WeightPrice::set((Parent.into(), 1_000_000_000_000)); - - let origin = one.clone(); - let fees = (Parent, 100).into(); - let message = Xcm::(vec![ - WithdrawAsset((Parent, 100).into()), // enough for 100 units of weight. - BuyExecution { fees, weight_limit: Limited(100) }, - Transact { - origin_type: OriginKind::Native, - require_weight_at_most: 50, - // call estimated at 50 but only takes 10. - call: TestCall::Any(50, Some(10)).encode().into(), - }, - RefundSurplus, - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: one.clone() }, - ]); - let weight_limit = 100; - let r = XcmExecutor::::execute_xcm(origin, message, weight_limit); - assert_eq!(r, Outcome::Complete(60)); - assert_eq!(assets(1), vec![(Parent, 40).into()]); -} - -#[test] -fn prepaid_result_of_query_should_get_free_execution() { - let query_id = 33; - // We put this in manually here, but normally this would be done at the point of crafting the message. - expect_response(query_id, Parent.into()); - - let the_response = Response::Assets((Parent, 100).into()); - let message = Xcm::(vec![QueryResponse { - query_id, - response: the_response.clone(), - max_weight: 10, - }]); - let weight_limit = 10; - - // First time the response gets through since we're expecting it... - let r = XcmExecutor::::execute_xcm(Parent, message.clone(), weight_limit); - assert_eq!(r, Outcome::Complete(10)); - assert_eq!(response(query_id).unwrap(), the_response); - - // Second time it doesn't, since we're not. - let r = XcmExecutor::::execute_xcm(Parent, message.clone(), weight_limit); - assert_eq!(r, Outcome::Error(XcmError::Barrier)); -} - -fn fungible_multi_asset(location: MultiLocation, amount: u128) -> MultiAsset { - (AssetId::from(location), Fungibility::Fungible(amount)).into() -} - -#[test] -fn weight_trader_tuple_should_work() { - pub const PARA_1: MultiLocation = X1(Parachain(1)).into(); - pub const PARA_2: MultiLocation = X1(Parachain(2)).into(); - - parameter_types! { - pub static HereWeightPrice: (AssetId, u128) = (Here.into().into(), WEIGHT_REF_TIME_PER_SECOND.into()); - pub static PARA1WeightPrice: (AssetId, u128) = (PARA_1.into(), WEIGHT_REF_TIME_PER_SECOND.into()); - } - - type Traders = ( - // trader one - FixedRateOfFungible, - // trader two - FixedRateOfFungible, - ); - - let mut traders = Traders::new(); - // trader one buys weight - assert_eq!( - traders.buy_weight(5, fungible_multi_asset(Here.into(), 10).into()), - Ok(fungible_multi_asset(Here.into(), 5).into()), - ); - // trader one refunds - assert_eq!(traders.refund_weight(2), Some(fungible_multi_asset(Here.into(), 2))); - - let mut traders = Traders::new(); - // trader one failed; trader two buys weight - assert_eq!( - traders.buy_weight(5, fungible_multi_asset(PARA_1, 10).into()), - Ok(fungible_multi_asset(PARA_1, 5).into()), - ); - // trader two refunds - assert_eq!(traders.refund_weight(2), Some(fungible_multi_asset(PARA_1, 2))); - - let mut traders = Traders::new(); - // all traders fails - assert_err!( - traders.buy_weight(5, fungible_multi_asset(PARA_2, 10).into()), - XcmError::TooExpensive, - ); - // and no refund - assert_eq!(traders.refund_weight(2), None); -} diff --git a/xcm/xcm-builder/src/tests/assets.rs b/xcm/xcm-builder/src/tests/assets.rs new file mode 100644 index 000000000000..57eacf00183b --- /dev/null +++ b/xcm/xcm-builder/src/tests/assets.rs @@ -0,0 +1,469 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use super::*; + +#[test] +fn exchange_asset_should_work() { + AllowUnpaidFrom::set(vec![Parent.into()]); + add_asset(Parent, (Parent, 1000u128)); + set_exchange_assets(vec![(Here, 100u128).into()]); + let message = Xcm(vec![ + WithdrawAsset((Parent, 100u128).into()), + SetAppendix( + vec![DepositAsset { assets: AllCounted(2).into(), beneficiary: Parent.into() }].into(), + ), + ExchangeAsset { + give: Definite((Parent, 50u128).into()), + want: (Here, 50u128).into(), + maximal: true, + }, + ]); + let hash = fake_message_hash(&message); + let r = + XcmExecutor::::execute_xcm(Parent, message, hash, Weight::from_parts(50, 50)); + assert_eq!(r, Outcome::Complete(Weight::from_parts(40, 40))); + assert_eq!(asset_list(Parent), vec![(Here, 100u128).into(), (Parent, 950u128).into()]); + assert_eq!(exchange_assets(), vec![(Parent, 50u128).into()].into()); +} + +#[test] +fn exchange_asset_without_maximal_should_work() { + AllowUnpaidFrom::set(vec![Parent.into()]); + add_asset(Parent, (Parent, 1000u128)); + set_exchange_assets(vec![(Here, 100u128).into()]); + let message = Xcm(vec![ + WithdrawAsset((Parent, 100u128).into()), + SetAppendix( + vec![DepositAsset { assets: AllCounted(2).into(), beneficiary: Parent.into() }].into(), + ), + ExchangeAsset { + give: Definite((Parent, 50).into()), + want: (Here, 50u128).into(), + maximal: false, + }, + ]); + let hash = fake_message_hash(&message); + let r = + XcmExecutor::::execute_xcm(Parent, message, hash, Weight::from_parts(50, 50)); + assert_eq!(r, Outcome::Complete(Weight::from_parts(40, 40))); + assert_eq!(asset_list(Parent), vec![(Here, 50u128).into(), (Parent, 950u128).into()]); + assert_eq!(exchange_assets(), vec![(Here, 50u128).into(), (Parent, 50u128).into()].into()); +} + +#[test] +fn exchange_asset_should_fail_when_no_deal_possible() { + AllowUnpaidFrom::set(vec![Parent.into()]); + add_asset(Parent, (Parent, 1000u128)); + set_exchange_assets(vec![(Here, 100u128).into()]); + let message = Xcm(vec![ + WithdrawAsset((Parent, 150u128).into()), + SetAppendix( + vec![DepositAsset { assets: AllCounted(2).into(), beneficiary: Parent.into() }].into(), + ), + ExchangeAsset { + give: Definite((Parent, 150u128).into()), + want: (Here, 150u128).into(), + maximal: false, + }, + ]); + let hash = fake_message_hash(&message); + let r = + XcmExecutor::::execute_xcm(Parent, message, hash, Weight::from_parts(50, 50)); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(40, 40), XcmError::NoDeal)); + assert_eq!(asset_list(Parent), vec![(Parent, 1000u128).into()]); + assert_eq!(exchange_assets(), vec![(Here, 100u128).into()].into()); +} + +#[test] +fn paying_reserve_deposit_should_work() { + AllowPaidFrom::set(vec![Parent.into()]); + add_reserve(Parent.into(), (Parent, WildFungible).into()); + WeightPrice::set((Parent.into(), 1_000_000_000_000, 1024 * 1024)); + + let fees = (Parent, 60u128).into(); + let message = Xcm(vec![ + ReserveAssetDeposited((Parent, 100u128).into()), + BuyExecution { fees, weight_limit: Limited(Weight::from_parts(30, 30)) }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() }, + ]); + let hash = fake_message_hash(&message); + let weight_limit = Weight::from_parts(50, 50); + let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); + assert_eq!(r, Outcome::Complete(Weight::from_parts(30, 30))); + assert_eq!(asset_list(Here), vec![(Parent, 40u128).into()]); +} + +#[test] +fn transfer_should_work() { + // we'll let them have message execution for free. + AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + // Child parachain #1 owns 1000 tokens held by us in reserve. + add_asset(Parachain(1), (Here, 1000)); + // They want to transfer 100 of them to their sibling parachain #2 + let message = Xcm(vec![TransferAsset { + assets: (Here, 100u128).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(), + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + assert_eq!( + asset_list(AccountIndex64 { index: 3, network: None }), + vec![(Here, 100u128).into()] + ); + assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]); + assert_eq!(sent_xcm(), vec![]); +} + +#[test] +fn reserve_transfer_should_work() { + AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + // Child parachain #1 owns 1000 tokens held by us in reserve. + add_asset(Parachain(1), (Here, 1000)); + // The remote account owned by gav. + let three: MultiLocation = X1(AccountIndex64 { index: 3, network: None }).into(); + + // They want to transfer 100 of our native asset from sovereign account of parachain #1 into #2 + // and let them know to hand it to account #3. + let message = Xcm(vec![TransferReserveAsset { + assets: (Here, 100u128).into(), + dest: Parachain(2).into(), + xcm: Xcm::<()>(vec![DepositAsset { + assets: AllCounted(1).into(), + beneficiary: three.clone(), + }]), + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + + let expected_msg = Xcm::<()>(vec![ + ReserveAssetDeposited((Parent, 100u128).into()), + ClearOrigin, + DepositAsset { assets: AllCounted(1).into(), beneficiary: three }, + ]); + let expected_hash = fake_message_hash(&expected_msg); + assert_eq!(asset_list(Parachain(2)), vec![(Here, 100).into()]); + assert_eq!(sent_xcm(), vec![(Parachain(2).into(), expected_msg, expected_hash)]); +} + +#[test] +fn burn_should_work() { + // we'll let them have message execution for free. + AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + // Child parachain #1 owns 1000 tokens held by us in reserve. + add_asset(Parachain(1), (Here, 1000)); + // They want to burn 100 of them + let message = Xcm(vec![ + WithdrawAsset((Here, 1000u128).into()), + BurnAsset((Here, 100u128).into()), + DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Parachain(1).into() }, + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(30, 30))); + assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]); + assert_eq!(sent_xcm(), vec![]); + + // Now they want to burn 1000 of them, which will actually only burn 900. + let message = Xcm(vec![ + WithdrawAsset((Here, 900u128).into()), + BurnAsset((Here, 1000u128).into()), + DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Parachain(1).into() }, + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(30, 30))); + assert_eq!(asset_list(Parachain(1)), vec![]); + assert_eq!(sent_xcm(), vec![]); +} + +#[test] +fn basic_asset_trap_should_work() { + // we'll let them have message execution for free. + AllowUnpaidFrom::set(vec![X1(Parachain(1)).into(), X1(Parachain(2)).into()]); + + // Child parachain #1 owns 1000 tokens held by us in reserve. + add_asset(Parachain(1), (Here, 1000)); + // They want to transfer 100 of them to their sibling parachain #2 but have a problem + let message = Xcm(vec![ + WithdrawAsset((Here, 100u128).into()), + DepositAsset { + assets: Wild(AllCounted(0)), // <<< 0 is an error. + beneficiary: AccountIndex64 { index: 3, network: None }.into(), + }, + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(20, 20), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(25, 25))); + assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]); + assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![]); + + // Incorrect ticket doesn't work. + let message = Xcm(vec![ + ClaimAsset { assets: (Here, 100u128).into(), ticket: GeneralIndex(1).into() }, + DepositAsset { + assets: Wild(AllCounted(1)), + beneficiary: AccountIndex64 { index: 3, network: None }.into(), + }, + ]); + let hash = fake_message_hash(&message); + let old_trapped_assets = TrappedAssets::get(); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(20, 20), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::UnknownClaim)); + assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]); + assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![]); + assert_eq!(old_trapped_assets, TrappedAssets::get()); + + // Incorrect origin doesn't work. + let message = Xcm(vec![ + ClaimAsset { assets: (Here, 100u128).into(), ticket: GeneralIndex(0).into() }, + DepositAsset { + assets: Wild(AllCounted(1)), + beneficiary: AccountIndex64 { index: 3, network: None }.into(), + }, + ]); + let hash = fake_message_hash(&message); + let old_trapped_assets = TrappedAssets::get(); + let r = XcmExecutor::::execute_xcm( + Parachain(2), + message, + hash, + Weight::from_parts(20, 20), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::UnknownClaim)); + assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]); + assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![]); + assert_eq!(old_trapped_assets, TrappedAssets::get()); + + // Incorrect assets doesn't work. + let message = Xcm(vec![ + ClaimAsset { assets: (Here, 101u128).into(), ticket: GeneralIndex(0).into() }, + DepositAsset { + assets: Wild(AllCounted(1)), + beneficiary: AccountIndex64 { index: 3, network: None }.into(), + }, + ]); + let hash = fake_message_hash(&message); + let old_trapped_assets = TrappedAssets::get(); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(20, 20), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::UnknownClaim)); + assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]); + assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![]); + assert_eq!(old_trapped_assets, TrappedAssets::get()); + + let message = Xcm(vec![ + ClaimAsset { assets: (Here, 100u128).into(), ticket: GeneralIndex(0).into() }, + DepositAsset { + assets: Wild(AllCounted(1)), + beneficiary: AccountIndex64 { index: 3, network: None }.into(), + }, + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(20, 20), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(20, 20))); + assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]); + assert_eq!( + asset_list(AccountIndex64 { index: 3, network: None }), + vec![(Here, 100u128).into()] + ); + + // Same again doesn't work :-) + let message = Xcm(vec![ + ClaimAsset { assets: (Here, 100u128).into(), ticket: GeneralIndex(0).into() }, + DepositAsset { + assets: Wild(AllCounted(1)), + beneficiary: AccountIndex64 { index: 3, network: None }.into(), + }, + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(20, 20), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::UnknownClaim)); +} + +#[test] +fn max_assets_limit_should_work() { + // we'll let them have message execution for free. + AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + // Child parachain #1 owns 1000 tokens held by us in reserve. + add_asset(Parachain(1), ([1u8; 32], 1000u128)); + add_asset(Parachain(1), ([2u8; 32], 1000u128)); + add_asset(Parachain(1), ([3u8; 32], 1000u128)); + add_asset(Parachain(1), ([4u8; 32], 1000u128)); + add_asset(Parachain(1), ([5u8; 32], 1000u128)); + add_asset(Parachain(1), ([6u8; 32], 1000u128)); + add_asset(Parachain(1), ([7u8; 32], 1000u128)); + add_asset(Parachain(1), ([8u8; 32], 1000u128)); + add_asset(Parachain(1), ([9u8; 32], 1000u128)); + + // Attempt to withdraw 8 (=2x4)different assets. This will succeed. + let message = Xcm(vec![ + WithdrawAsset(([1u8; 32], 100u128).into()), + WithdrawAsset(([2u8; 32], 100u128).into()), + WithdrawAsset(([3u8; 32], 100u128).into()), + WithdrawAsset(([4u8; 32], 100u128).into()), + WithdrawAsset(([5u8; 32], 100u128).into()), + WithdrawAsset(([6u8; 32], 100u128).into()), + WithdrawAsset(([7u8; 32], 100u128).into()), + WithdrawAsset(([8u8; 32], 100u128).into()), + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(100, 100), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(85, 85))); + + // Attempt to withdraw 9 different assets will fail. + let message = Xcm(vec![ + WithdrawAsset(([1u8; 32], 100u128).into()), + WithdrawAsset(([2u8; 32], 100u128).into()), + WithdrawAsset(([3u8; 32], 100u128).into()), + WithdrawAsset(([4u8; 32], 100u128).into()), + WithdrawAsset(([5u8; 32], 100u128).into()), + WithdrawAsset(([6u8; 32], 100u128).into()), + WithdrawAsset(([7u8; 32], 100u128).into()), + WithdrawAsset(([8u8; 32], 100u128).into()), + WithdrawAsset(([9u8; 32], 100u128).into()), + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(100, 100), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(95, 95), XcmError::HoldingWouldOverflow)); + + // Attempt to withdraw 4 different assets and then the same 4 and then a different 4 will succeed. + let message = Xcm(vec![ + WithdrawAsset(([1u8; 32], 100u128).into()), + WithdrawAsset(([2u8; 32], 100u128).into()), + WithdrawAsset(([3u8; 32], 100u128).into()), + WithdrawAsset(([4u8; 32], 100u128).into()), + WithdrawAsset(([1u8; 32], 100u128).into()), + WithdrawAsset(([2u8; 32], 100u128).into()), + WithdrawAsset(([3u8; 32], 100u128).into()), + WithdrawAsset(([4u8; 32], 100u128).into()), + WithdrawAsset(([5u8; 32], 100u128).into()), + WithdrawAsset(([6u8; 32], 100u128).into()), + WithdrawAsset(([7u8; 32], 100u128).into()), + WithdrawAsset(([8u8; 32], 100u128).into()), + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(200, 200), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(125, 125))); + + // Attempt to withdraw 4 different assets and then a different 4 and then the same 4 will fail. + let message = Xcm(vec![ + WithdrawAsset(([1u8; 32], 100u128).into()), + WithdrawAsset(([2u8; 32], 100u128).into()), + WithdrawAsset(([3u8; 32], 100u128).into()), + WithdrawAsset(([4u8; 32], 100u128).into()), + WithdrawAsset(([5u8; 32], 100u128).into()), + WithdrawAsset(([6u8; 32], 100u128).into()), + WithdrawAsset(([7u8; 32], 100u128).into()), + WithdrawAsset(([8u8; 32], 100u128).into()), + WithdrawAsset(([1u8; 32], 100u128).into()), + WithdrawAsset(([2u8; 32], 100u128).into()), + WithdrawAsset(([3u8; 32], 100u128).into()), + WithdrawAsset(([4u8; 32], 100u128).into()), + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(200, 200), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(95, 95), XcmError::HoldingWouldOverflow)); + + // Attempt to withdraw 4 different assets and then a different 4 and then the same 4 will fail. + let message = Xcm(vec![ + WithdrawAsset(MultiAssets::from(vec![ + ([1u8; 32], 100u128).into(), + ([2u8; 32], 100u128).into(), + ([3u8; 32], 100u128).into(), + ([4u8; 32], 100u128).into(), + ([5u8; 32], 100u128).into(), + ([6u8; 32], 100u128).into(), + ([7u8; 32], 100u128).into(), + ([8u8; 32], 100u128).into(), + ])), + WithdrawAsset(([1u8; 32], 100u128).into()), + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(200, 200), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(25, 25), XcmError::HoldingWouldOverflow)); +} diff --git a/xcm/xcm-builder/src/tests/barriers.rs b/xcm/xcm-builder/src/tests/barriers.rs new file mode 100644 index 000000000000..a7889c7e212d --- /dev/null +++ b/xcm/xcm-builder/src/tests/barriers.rs @@ -0,0 +1,278 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use super::*; + +#[test] +fn take_weight_credit_barrier_should_work() { + let mut message = + Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]); + let mut weight_credit = Weight::from_parts(10, 10); + let r = TakeWeightCredit::should_execute( + &Parent.into(), + message.inner_mut(), + Weight::from_parts(10, 10), + &mut weight_credit, + ); + assert_eq!(r, Ok(())); + assert_eq!(weight_credit, Weight::zero()); + + let r = TakeWeightCredit::should_execute( + &Parent.into(), + message.inner_mut(), + Weight::from_parts(10, 10), + &mut weight_credit, + ); + assert_eq!(r, Err(())); + assert_eq!(weight_credit, Weight::zero()); +} + +#[test] +fn computed_origin_should_work() { + let mut message = Xcm::<()>(vec![ + UniversalOrigin(GlobalConsensus(Kusama)), + DescendOrigin(Parachain(100).into()), + DescendOrigin(PalletInstance(69).into()), + WithdrawAsset((Parent, 100).into()), + BuyExecution { + fees: (Parent, 100).into(), + weight_limit: Limited(Weight::from_parts(100, 100)), + }, + TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }, + ]); + + AllowPaidFrom::set(vec![( + Parent, + Parent, + GlobalConsensus(Kusama), + Parachain(100), + PalletInstance(69), + ) + .into()]); + + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &Parent.into(), + message.inner_mut(), + Weight::from_parts(100, 100), + &mut Weight::zero(), + ); + assert_eq!(r, Err(())); + + let r = WithComputedOrigin::< + AllowTopLevelPaidExecutionFrom>, + ExecutorUniversalLocation, + ConstU32<2>, + >::should_execute( + &Parent.into(), + message.inner_mut(), + Weight::from_parts(100, 100), + &mut Weight::zero(), + ); + assert_eq!(r, Err(())); + + let r = WithComputedOrigin::< + AllowTopLevelPaidExecutionFrom>, + ExecutorUniversalLocation, + ConstU32<5>, + >::should_execute( + &Parent.into(), + message.inner_mut(), + Weight::from_parts(100, 100), + &mut Weight::zero(), + ); + assert_eq!(r, Ok(())); +} + +#[test] +fn allow_unpaid_should_work() { + let mut message = + Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]); + + AllowUnpaidFrom::set(vec![Parent.into()]); + + let r = AllowUnpaidExecutionFrom::>::should_execute( + &Parachain(1).into(), + message.inner_mut(), + Weight::from_parts(10, 10), + &mut Weight::zero(), + ); + assert_eq!(r, Err(())); + + let r = AllowUnpaidExecutionFrom::>::should_execute( + &Parent.into(), + message.inner_mut(), + Weight::from_parts(10, 10), + &mut Weight::zero(), + ); + assert_eq!(r, Ok(())); +} + +#[test] +fn allow_explicit_unpaid_should_work() { + let mut bad_message1 = + Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]); + + let mut bad_message2 = Xcm::<()>(vec![ + UnpaidExecution { + weight_limit: Limited(Weight::from_parts(10, 10)), + check_origin: Some(Parent.into()), + }, + TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }, + ]); + + let mut good_message = Xcm::<()>(vec![ + UnpaidExecution { + weight_limit: Limited(Weight::from_parts(20, 20)), + check_origin: Some(Parent.into()), + }, + TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }, + ]); + + AllowExplicitUnpaidFrom::set(vec![Parent.into()]); + + let r = AllowExplicitUnpaidExecutionFrom::>::should_execute( + &Parachain(1).into(), + good_message.inner_mut(), + Weight::from_parts(20, 20), + &mut Weight::zero(), + ); + assert_eq!(r, Err(())); + + let r = AllowExplicitUnpaidExecutionFrom::>::should_execute( + &Parent.into(), + bad_message1.inner_mut(), + Weight::from_parts(20, 20), + &mut Weight::zero(), + ); + assert_eq!(r, Err(())); + + let r = AllowExplicitUnpaidExecutionFrom::>::should_execute( + &Parent.into(), + bad_message2.inner_mut(), + Weight::from_parts(20, 20), + &mut Weight::zero(), + ); + assert_eq!(r, Err(())); + + let r = AllowExplicitUnpaidExecutionFrom::>::should_execute( + &Parent.into(), + good_message.inner_mut(), + Weight::from_parts(20, 20), + &mut Weight::zero(), + ); + assert_eq!(r, Ok(())); + + let mut message_with_different_weight_parts = Xcm::<()>(vec![ + UnpaidExecution { + weight_limit: Limited(Weight::from_parts(20, 10)), + check_origin: Some(Parent.into()), + }, + TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }, + ]); + + let r = AllowExplicitUnpaidExecutionFrom::>::should_execute( + &Parent.into(), + message_with_different_weight_parts.inner_mut(), + Weight::from_parts(20, 20), + &mut Weight::zero(), + ); + assert_eq!(r, Err(())); + + let r = AllowExplicitUnpaidExecutionFrom::>::should_execute( + &Parent.into(), + message_with_different_weight_parts.inner_mut(), + Weight::from_parts(10, 10), + &mut Weight::zero(), + ); + assert_eq!(r, Ok(())); +} + +#[test] +fn allow_paid_should_work() { + AllowPaidFrom::set(vec![Parent.into()]); + + let mut message = + Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]); + + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &Parachain(1).into(), + message.inner_mut(), + Weight::from_parts(10, 10), + &mut Weight::zero(), + ); + assert_eq!(r, Err(())); + + let fees = (Parent, 1).into(); + let mut underpaying_message = Xcm::<()>(vec![ + ReserveAssetDeposited((Parent, 100).into()), + BuyExecution { fees, weight_limit: Limited(Weight::from_parts(20, 20)) }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() }, + ]); + + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &Parent.into(), + underpaying_message.inner_mut(), + Weight::from_parts(30, 30), + &mut Weight::zero(), + ); + assert_eq!(r, Err(())); + + let fees = (Parent, 1).into(); + let mut paying_message = Xcm::<()>(vec![ + ReserveAssetDeposited((Parent, 100).into()), + BuyExecution { fees, weight_limit: Limited(Weight::from_parts(30, 30)) }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() }, + ]); + + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &Parachain(1).into(), + paying_message.inner_mut(), + Weight::from_parts(30, 30), + &mut Weight::zero(), + ); + assert_eq!(r, Err(())); + + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &Parent.into(), + paying_message.inner_mut(), + Weight::from_parts(30, 30), + &mut Weight::zero(), + ); + assert_eq!(r, Ok(())); + + let fees = (Parent, 1).into(); + let mut paying_message_with_different_weight_parts = Xcm::<()>(vec![ + WithdrawAsset((Parent, 100).into()), + BuyExecution { fees, weight_limit: Limited(Weight::from_parts(20, 10)) }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() }, + ]); + + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &Parent.into(), + paying_message_with_different_weight_parts.inner_mut(), + Weight::from_parts(20, 20), + &mut Weight::zero(), + ); + assert_eq!(r, Err(())); + + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &Parent.into(), + paying_message_with_different_weight_parts.inner_mut(), + Weight::from_parts(10, 10), + &mut Weight::zero(), + ); + assert_eq!(r, Ok(())) +} diff --git a/xcm/xcm-builder/src/tests/basic.rs b/xcm/xcm-builder/src/tests/basic.rs new file mode 100644 index 000000000000..6a821e22b66a --- /dev/null +++ b/xcm/xcm-builder/src/tests/basic.rs @@ -0,0 +1,106 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use super::*; + +#[test] +fn basic_setup_works() { + add_reserve(Parent.into(), Wild((Parent, WildFungible).into())); + assert!( + ::IsReserve::contains(&(Parent, 100u128).into(), &Parent.into(),) + ); + + assert_eq!(to_account(Parachain(1)), Ok(1001)); + assert_eq!(to_account(Parachain(50)), Ok(1050)); + assert_eq!(to_account((Parent, Parachain(1))), Ok(2001)); + assert_eq!(to_account((Parent, Parachain(50))), Ok(2050)); + assert_eq!( + to_account(MultiLocation::new(0, X1(AccountIndex64 { index: 1, network: None }))), + Ok(1), + ); + assert_eq!( + to_account(MultiLocation::new(0, X1(AccountIndex64 { index: 42, network: None }))), + Ok(42), + ); + assert_eq!(to_account(Here), Ok(3000)); +} + +#[test] +fn weigher_should_work() { + let mut message = Xcm(vec![ + ReserveAssetDeposited((Parent, 100u128).into()), + BuyExecution { + fees: (Parent, 1u128).into(), + weight_limit: Limited(Weight::from_parts(30, 30)), + }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() }, + ]); + assert_eq!( + ::Weigher::weight(&mut message), + Ok(Weight::from_parts(30, 30)) + ); +} + +#[test] +fn code_registers_should_work() { + // we'll let them have message execution for free. + AllowUnpaidFrom::set(vec![Here.into()]); + // We own 1000 of our tokens. + add_asset(Here, (Here, 21u128)); + let mut message = Xcm(vec![ + // Set our error handler - this will fire only on the second message, when there's an error + SetErrorHandler(Xcm(vec![ + TransferAsset { + assets: (Here, 2u128).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(), + }, + // It was handled fine. + ClearError, + ])), + // Set the appendix - this will always fire. + SetAppendix(Xcm(vec![TransferAsset { + assets: (Here, 4u128).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(), + }])), + // First xfer always works ok + TransferAsset { + assets: (Here, 1u128).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(), + }, + // Second xfer results in error on the second message - our error handler will fire. + TransferAsset { + assets: (Here, 8u128).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(), + }, + ]); + // Weight limit of 70 is needed. + let limit = ::Weigher::weight(&mut message).unwrap(); + assert_eq!(limit, Weight::from_parts(70, 70)); + + let hash = fake_message_hash(&message); + + let r = XcmExecutor::::execute_xcm(Here, message.clone(), hash, limit); + assert_eq!(r, Outcome::Complete(Weight::from_parts(50, 50))); // We don't pay the 20 weight for the error handler. + assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 13u128).into()]); + assert_eq!(asset_list(Here), vec![(Here, 8u128).into()]); + assert_eq!(sent_xcm(), vec![]); + + let r = XcmExecutor::::execute_xcm(Here, message, hash, limit); + assert_eq!(r, Outcome::Complete(Weight::from_parts(70, 70))); // We pay the full weight here. + assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 20u128).into()]); + assert_eq!(asset_list(Here), vec![(Here, 1u128).into()]); + assert_eq!(sent_xcm(), vec![]); +} diff --git a/xcm/xcm-builder/src/tests/bridging/local_para_para.rs b/xcm/xcm-builder/src/tests/bridging/local_para_para.rs new file mode 100644 index 000000000000..a2ab952b16d6 --- /dev/null +++ b/xcm/xcm-builder/src/tests/bridging/local_para_para.rs @@ -0,0 +1,112 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! This test is when we're sending an XCM from a parachain which hosts a bridge to another +//! network's bridge parachain. The destination of the XCM is within the global consensus of the +//! remote side of the bridge. + +use super::*; + +parameter_types! { + pub UniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(1)); + pub RemoteUniversalLocation: Junctions = X2(GlobalConsensus(Remote::get()), Parachain(1)); +} +type TheBridge = + TestBridge>; +type Router = UnpaidLocalExporter, UniversalLocation>; + +/// ```nocompile +/// local | remote +/// | +/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get()) +/// | +/// | +/// | +/// | +/// Parachain(1) ===> Parachain(1) +/// ``` +#[test] +fn sending_to_bridged_chain_works() { + let msg = Xcm(vec![Trap(1)]); + let dest = (Parent, Parent, Remote::get(), Parachain(1)).into(); + assert_eq!(send_xcm::(dest, msg).unwrap().1, (Here, 100).into()); + assert_eq!(TheBridge::service(), 1); + assert_eq!( + take_received_remote_messages(), + vec![( + Here.into(), + Xcm(vec![ + UniversalOrigin(Local::get().into()), + DescendOrigin(Parachain(1).into()), + Trap(1), + ]) + )] + ); +} + +/// ```nocompile +/// local | remote +/// | +/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get()) +/// | +/// | +/// | +/// | +/// Parachain(1) ===> Parachain(1) ==> Parachain(1000) +/// ``` +#[test] +fn sending_to_parachain_of_bridged_chain_works() { + let msg = Xcm(vec![Trap(1)]); + let dest = (Parent, Parent, Remote::get(), Parachain(1000)).into(); + assert_eq!(send_xcm::(dest, msg).unwrap().1, (Here, 100).into()); + assert_eq!(TheBridge::service(), 1); + let expected = vec![( + (Parent, Parachain(1000)).into(), + Xcm(vec![ + UniversalOrigin(Local::get().into()), + DescendOrigin(Parachain(1).into()), + Trap(1), + ]), + )]; + assert_eq!(take_received_remote_messages(), expected); +} + +/// ```nocompile +/// local | remote +/// | +/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get()) +/// | /\ +/// | || +/// | || +/// | || +/// Parachain(1) ===> Parachain(1) +/// ``` +#[test] +fn sending_to_relay_chain_of_bridged_chain_works() { + let msg = Xcm(vec![Trap(1)]); + let dest = (Parent, Parent, Remote::get()).into(); + assert_eq!(send_xcm::(dest, msg).unwrap().1, (Here, 100).into()); + assert_eq!(TheBridge::service(), 1); + let expected = vec![( + Parent.into(), + Xcm(vec![ + UniversalOrigin(Local::get().into()), + DescendOrigin(Parachain(1).into()), + Trap(1), + ]), + )]; + assert_eq!(take_received_remote_messages(), expected); +} diff --git a/xcm/xcm-builder/src/tests/bridging/local_relay_relay.rs b/xcm/xcm-builder/src/tests/bridging/local_relay_relay.rs new file mode 100644 index 000000000000..20b029526e9f --- /dev/null +++ b/xcm/xcm-builder/src/tests/bridging/local_relay_relay.rs @@ -0,0 +1,70 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! This test is when we're sending an XCM from a relay-chain which hosts a bridge to another +//! relay-chain. The destination of the XCM is within the global consensus of the +//! remote side of the bridge. + +use super::*; + +parameter_types! { + pub UniversalLocation: Junctions = X1(GlobalConsensus(Local::get())); + pub RemoteUniversalLocation: Junctions = X1(GlobalConsensus(Remote::get())); +} +type TheBridge = + TestBridge>; +type Router = UnpaidLocalExporter, UniversalLocation>; + +/// ```nocompile +/// local | remote +/// | +/// GlobalConsensus(Local::get()) ========> GlobalConsensus(Remote::get()) +/// | +/// ``` +#[test] +fn sending_to_bridged_chain_works() { + let msg = Xcm(vec![Trap(1)]); + assert_eq!( + send_xcm::((Parent, Remote::get()).into(), msg).unwrap().1, + (Here, 100).into() + ); + assert_eq!(TheBridge::service(), 1); + assert_eq!( + take_received_remote_messages(), + vec![(Here.into(), Xcm(vec![UniversalOrigin(Local::get().into()), Trap(1)]))] + ); +} + +/// ```nocompile +/// local | remote +/// | +/// GlobalConsensus(Local::get()) ========> GlobalConsensus(Remote::get()) +/// | || +/// | || +/// | || +/// | \/ +/// | Parachain(1000) +/// ``` +#[test] +fn sending_to_parachain_of_bridged_chain_works() { + let msg = Xcm(vec![Trap(1)]); + let dest = (Parent, Remote::get(), Parachain(1000)).into(); + assert_eq!(send_xcm::(dest, msg).unwrap().1, (Here, 100).into()); + assert_eq!(TheBridge::service(), 1); + let expected = + vec![(Parachain(1000).into(), Xcm(vec![UniversalOrigin(Local::get().into()), Trap(1)]))]; + assert_eq!(take_received_remote_messages(), expected); +} diff --git a/xcm/xcm-builder/src/tests/bridging/mod.rs b/xcm/xcm-builder/src/tests/bridging/mod.rs new file mode 100644 index 000000000000..0ca3733c108b --- /dev/null +++ b/xcm/xcm-builder/src/tests/bridging/mod.rs @@ -0,0 +1,194 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! Tests specific to the bridging primitives + +use super::mock::*; +use crate::universal_exports::*; +use frame_support::{parameter_types, traits::Get}; +use std::{cell::RefCell, marker::PhantomData}; +use xcm_executor::{ + traits::{export_xcm, validate_export}, + XcmExecutor, +}; +use SendError::*; + +mod local_para_para; +mod local_relay_relay; +mod paid_remote_relay_relay; +mod remote_para_para; +mod remote_para_para_via_relay; +mod remote_relay_relay; + +parameter_types! { + pub Local: NetworkId = ByGenesis([0; 32]); + pub Remote: NetworkId = ByGenesis([1; 32]); + pub Price: MultiAssets = MultiAssets::from((Here, 100u128)); +} + +std::thread_local! { + static BRIDGE_TRAFFIC: RefCell>> = RefCell::new(Vec::new()); +} + +struct TestBridge(PhantomData); +impl TestBridge { + fn service() -> u64 { + BRIDGE_TRAFFIC + .with(|t| t.borrow_mut().drain(..).map(|b| D::dispatch_blob(b).map_or(0, |()| 1)).sum()) + } +} +impl HaulBlob for TestBridge { + fn haul_blob(blob: Vec) -> Result<(), HaulBlobError> { + BRIDGE_TRAFFIC.with(|t| t.borrow_mut().push(blob)); + Ok(()) + } +} + +std::thread_local! { + static REMOTE_INCOMING_XCM: RefCell)>> = RefCell::new(Vec::new()); +} +struct TestRemoteIncomingRouter; +impl SendXcm for TestRemoteIncomingRouter { + type Ticket = (MultiLocation, Xcm<()>); + fn validate( + dest: &mut Option, + msg: &mut Option>, + ) -> SendResult<(MultiLocation, Xcm<()>)> { + let pair = (dest.take().unwrap(), msg.take().unwrap()); + Ok((pair, MultiAssets::new())) + } + fn deliver(pair: (MultiLocation, Xcm<()>)) -> Result { + let hash = fake_message_hash(&pair.1); + REMOTE_INCOMING_XCM.with(|q| q.borrow_mut().push(pair)); + Ok(hash) + } +} + +fn take_received_remote_messages() -> Vec<(MultiLocation, Xcm<()>)> { + REMOTE_INCOMING_XCM.with(|r| r.replace(vec![])) +} + +/// This is a dummy router which accepts messages destined for `Remote` from `Local` +/// and then executes them for free in a context simulated to be like that of our `Remote`. +struct UnpaidExecutingRouter( + PhantomData<(Local, Remote, RemoteExporter)>, +); + +fn price( + n: NetworkId, + c: u32, + s: &InteriorMultiLocation, + d: &InteriorMultiLocation, + m: &Xcm<()>, +) -> Result { + Ok(validate_export::(n, c, s.clone(), d.clone(), m.clone())?.1) +} + +fn deliver( + n: NetworkId, + c: u32, + s: InteriorMultiLocation, + d: InteriorMultiLocation, + m: Xcm<()>, +) -> Result { + export_xcm::(n, c, s, d, m).map(|(hash, _)| hash) +} + +impl, Remote: Get, RemoteExporter: ExportXcm> SendXcm + for UnpaidExecutingRouter +{ + type Ticket = Xcm<()>; + + fn validate( + destination: &mut Option, + message: &mut Option>, + ) -> SendResult> { + let expect_dest = Remote::get().relative_to(&Local::get()); + if destination.as_ref().ok_or(MissingArgument)? != &expect_dest { + return Err(NotApplicable) + } + let message = message.take().ok_or(MissingArgument)?; + Ok((message, MultiAssets::new())) + } + + fn deliver(message: Xcm<()>) -> Result { + // We now pretend that the message was delivered from `Local` to `Remote`, and execute + // so we need to ensure that the `TestConfig` is set up properly for executing as + // though it is `Remote`. + ExecutorUniversalLocation::set(Remote::get()); + let origin = Local::get().relative_to(&Remote::get()); + AllowUnpaidFrom::set(vec![origin.clone()]); + set_exporter_override(price::, deliver::); + // The we execute it: + let hash = fake_message_hash(&message); + let outcome = XcmExecutor::::execute_xcm( + origin, + message.into(), + hash, + Weight::from_parts(2_000_000_000_000, 2_000_000_000_000), + ); + match outcome { + Outcome::Complete(..) => Ok(hash), + Outcome::Incomplete(..) => Err(Transport("Error executing")), + Outcome::Error(..) => Err(Transport("Unable to execute")), + } + } +} + +/// This is a dummy router which accepts messages destined for `Remote` from `Local` +/// and then executes them in a context simulated to be like that of our `Remote`. Payment is +/// needed. +struct ExecutingRouter(PhantomData<(Local, Remote, RemoteExporter)>); +impl, Remote: Get, RemoteExporter: ExportXcm> SendXcm + for ExecutingRouter +{ + type Ticket = Xcm<()>; + + fn validate( + destination: &mut Option, + message: &mut Option>, + ) -> SendResult> { + let expect_dest = Remote::get().relative_to(&Local::get()); + if destination.as_ref().ok_or(MissingArgument)? != &expect_dest { + return Err(NotApplicable) + } + let message = message.take().ok_or(MissingArgument)?; + Ok((message, MultiAssets::new())) + } + + fn deliver(message: Xcm<()>) -> Result { + // We now pretend that the message was delivered from `Local` to `Remote`, and execute + // so we need to ensure that the `TestConfig` is set up properly for executing as + // though it is `Remote`. + ExecutorUniversalLocation::set(Remote::get()); + let origin = Local::get().relative_to(&Remote::get()); + AllowPaidFrom::set(vec![origin.clone()]); + set_exporter_override(price::, deliver::); + // Then we execute it: + let hash = fake_message_hash(&message); + let outcome = XcmExecutor::::execute_xcm( + origin, + message.into(), + hash, + Weight::from_parts(2_000_000_000_000, 2_000_000_000_000), + ); + match outcome { + Outcome::Complete(..) => Ok(hash), + Outcome::Incomplete(..) => Err(Transport("Error executing")), + Outcome::Error(..) => Err(Transport("Unable to execute")), + } + } +} diff --git a/xcm/xcm-builder/src/tests/bridging/paid_remote_relay_relay.rs b/xcm/xcm-builder/src/tests/bridging/paid_remote_relay_relay.rs new file mode 100644 index 000000000000..d31c46c734fb --- /dev/null +++ b/xcm/xcm-builder/src/tests/bridging/paid_remote_relay_relay.rs @@ -0,0 +1,124 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! This test is when we're sending an XCM from a parachain whose relay-chain hosts a bridge to +//! another relay-chain. The destination of the XCM is within the global consensus of the +//! remote side of the bridge. +//! +//! The Relay-chain here requires payment by the parachain for use of the bridge. This is expressed +//! under the standard XCM weight and the weight pricing. + +use super::*; + +parameter_types! { + pub UniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(100)); + pub RelayUniversalLocation: Junctions = X1(GlobalConsensus(Local::get())); + pub RemoteUniversalLocation: Junctions = X1(GlobalConsensus(Remote::get())); + pub static BridgeTable: Vec<(NetworkId, MultiLocation, Option)> + = vec![(Remote::get(), MultiLocation::parent(), Some((Parent, 200u128).into()))]; + // ^^^ 100 to use the bridge (export) and 100 for the remote execution weight (5 instructions + // x (10 + 10) weight each). +} +type TheBridge = + TestBridge>; +type RelayExporter = HaulBlobExporter; +type LocalInnerRouter = ExecutingRouter; +type LocalBridgeRouter = SovereignPaidRemoteExporter< + NetworkExportTable, + LocalInnerRouter, + UniversalLocation, +>; +type LocalRouter = (LocalInnerRouter, LocalBridgeRouter); + +/// ```nocompile +/// local | remote +/// | +/// GlobalConsensus(Local::get()) ========> GlobalConsensus(Remote::get()) +/// /\ | +/// || | +/// || | +/// || | +/// Parachain(100) | +/// ``` +#[test] +fn sending_to_bridged_chain_works() { + let dest: MultiLocation = (Parent, Parent, Remote::get()).into(); + // Routing won't work if we don't have enough funds. + assert_eq!( + send_xcm::(dest.clone(), Xcm(vec![Trap(1)])), + Err(SendError::Transport("Error executing")), + ); + + // Initialize the local relay so that our parachain has funds to pay for export. + add_asset(Parachain(100), (Here, 1000u128)); + + let msg = Xcm(vec![Trap(1)]); + assert_eq!(send_xcm::(dest, msg).unwrap().1, (Parent, 200u128).into()); + assert_eq!(TheBridge::service(), 1); + assert_eq!( + take_received_remote_messages(), + vec![( + Here.into(), + Xcm(vec![ + UniversalOrigin(Local::get().into()), + DescendOrigin(Parachain(100).into()), + Trap(1), + ]) + )] + ); + + // The export cost 50 ref time and 50 proof size weight units (and thus 100 units of balance). + assert_eq!(asset_list(Parachain(100)), vec![(Here, 800u128).into()]); +} + +/// ```nocompile +/// local | remote +/// | +/// GlobalConsensus(Local::get()) ========> GlobalConsensus(Remote::get()) +/// /\ | || +/// || | || +/// || | || +/// || | \/ +/// Parachain(100) | Parachain(100) +/// ``` +#[test] +fn sending_to_parachain_of_bridged_chain_works() { + let dest: MultiLocation = (Parent, Parent, Remote::get(), Parachain(100)).into(); + // Routing won't work if we don't have enough funds. + assert_eq!( + send_xcm::(dest.clone(), Xcm(vec![Trap(1)])), + Err(SendError::Transport("Error executing")), + ); + + // Initialize the local relay so that our parachain has funds to pay for export. + add_asset(Parachain(100), (Here, 1000u128)); + + let msg = Xcm(vec![Trap(1)]); + assert_eq!(send_xcm::(dest, msg).unwrap().1, (Parent, 200u128).into()); + assert_eq!(TheBridge::service(), 1); + let expected = vec![( + Parachain(100).into(), + Xcm(vec![ + UniversalOrigin(Local::get().into()), + DescendOrigin(Parachain(100).into()), + Trap(1), + ]), + )]; + assert_eq!(take_received_remote_messages(), expected); + + // The export cost 50 ref time and 50 proof size weight units (and thus 100 units of balance). + assert_eq!(asset_list(Parachain(100)), vec![(Here, 800u128).into()]); +} diff --git a/xcm/xcm-builder/src/tests/bridging/remote_para_para.rs b/xcm/xcm-builder/src/tests/bridging/remote_para_para.rs new file mode 100644 index 000000000000..648807763b2a --- /dev/null +++ b/xcm/xcm-builder/src/tests/bridging/remote_para_para.rs @@ -0,0 +1,124 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! This test is when we're sending an XCM from a parachain whose sibling parachain hosts a +//! bridge to a parachain from another global consensus. The destination of the XCM is within +//! the global consensus of the remote side of the bridge. + +use super::*; + +parameter_types! { + pub UniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(1000)); + pub ParaBridgeUniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(1)); + pub RemoteParaBridgeUniversalLocation: Junctions = X2(GlobalConsensus(Remote::get()), Parachain(1)); + pub BridgeTable: Vec<(NetworkId, MultiLocation, Option)> + = vec![(Remote::get(), (Parent, Parachain(1)).into(), None)]; +} +type TheBridge = + TestBridge>; +type RelayExporter = HaulBlobExporter; +type LocalInnerRouter = + UnpaidExecutingRouter; +type LocalBridgingRouter = + UnpaidRemoteExporter, LocalInnerRouter, UniversalLocation>; +type LocalRouter = (LocalInnerRouter, LocalBridgingRouter); + +/// ```nocompile +/// local | remote +/// | +/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get()) +/// | +/// | +/// | +/// | +/// Parachain(1000) ===> Parachain(1) ===> Parachain(1) +/// ``` +#[test] +fn sending_to_bridged_chain_works() { + let msg = Xcm(vec![Trap(1)]); + assert_eq!( + send_xcm::((Parent, Parent, Remote::get(), Parachain(1)).into(), msg) + .unwrap() + .1, + MultiAssets::new() + ); + assert_eq!(TheBridge::service(), 1); + assert_eq!( + take_received_remote_messages(), + vec![( + Here.into(), + Xcm(vec![ + UniversalOrigin(Local::get().into()), + DescendOrigin(Parachain(1000).into()), + Trap(1) + ]) + )] + ); +} + +/// ```nocompile +/// local | remote +/// | +/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get()) +/// | +/// | +/// | +/// | +/// Parachain(1000) ===> Parachain(1) ===> Parachain(1) ===> Parachain(1000) +/// ``` +#[test] +fn sending_to_sibling_of_bridged_chain_works() { + let msg = Xcm(vec![Trap(1)]); + let dest = (Parent, Parent, Remote::get(), Parachain(1000)).into(); + assert_eq!(send_xcm::(dest, msg).unwrap().1, MultiAssets::new()); + assert_eq!(TheBridge::service(), 1); + let expected = vec![( + (Parent, Parachain(1000)).into(), + Xcm(vec![ + UniversalOrigin(Local::get().into()), + DescendOrigin(Parachain(1000).into()), + Trap(1), + ]), + )]; + assert_eq!(take_received_remote_messages(), expected); +} + +/// ```nocompile +/// local | remote +/// | +/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get()) +/// | /\ +/// | || +/// | || +/// | || +/// Parachain(1000) ===> Parachain(1) ===> Parachain(1) +/// ``` +#[test] +fn sending_to_relay_of_bridged_chain_works() { + let msg = Xcm(vec![Trap(1)]); + let dest = (Parent, Parent, Remote::get()).into(); + assert_eq!(send_xcm::(dest, msg).unwrap().1, MultiAssets::new()); + assert_eq!(TheBridge::service(), 1); + let expected = vec![( + Parent.into(), + Xcm(vec![ + UniversalOrigin(Local::get().into()), + DescendOrigin(Parachain(1000).into()), + Trap(1), + ]), + )]; + assert_eq!(take_received_remote_messages(), expected); +} diff --git a/xcm/xcm-builder/src/tests/bridging/remote_para_para_via_relay.rs b/xcm/xcm-builder/src/tests/bridging/remote_para_para_via_relay.rs new file mode 100644 index 000000000000..0a6e0bfb73dd --- /dev/null +++ b/xcm/xcm-builder/src/tests/bridging/remote_para_para_via_relay.rs @@ -0,0 +1,106 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! This test is when we're sending an XCM from a relay-chain whose child parachain hosts a +//! bridge to a parachain from another global consensus. The destination of the XCM is within +//! the global consensus of the remote side of the bridge. + +use super::*; + +parameter_types! { + pub UniversalLocation: Junctions = X1(GlobalConsensus(Local::get())); + pub ParaBridgeUniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(1)); + pub RemoteParaBridgeUniversalLocation: Junctions = X2(GlobalConsensus(Remote::get()), Parachain(1)); + pub BridgeTable: Vec<(NetworkId, MultiLocation, Option)> + = vec![(Remote::get(), Parachain(1).into(), None)]; +} +type TheBridge = + TestBridge>; +type RelayExporter = HaulBlobExporter; +type LocalInnerRouter = + UnpaidExecutingRouter; +type LocalBridgingRouter = + UnpaidRemoteExporter, LocalInnerRouter, UniversalLocation>; +type LocalRouter = (LocalInnerRouter, LocalBridgingRouter); + +/// ```nocompile +/// local | remote +/// | +/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get()) +/// || | +/// || | +/// || | +/// \/ | +/// Parachain(1) ===> Parachain(1) +/// ``` +#[test] +fn sending_to_bridged_chain_works() { + let msg = Xcm(vec![Trap(1)]); + assert_eq!( + send_xcm::((Parent, Remote::get(), Parachain(1)).into(), msg) + .unwrap() + .1, + MultiAssets::new() + ); + assert_eq!(TheBridge::service(), 1); + assert_eq!( + take_received_remote_messages(), + vec![(Here.into(), Xcm(vec![UniversalOrigin(Local::get().into()), Trap(1)]))] + ); +} + +/// ```nocompile +/// local | remote +/// | +/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get()) +/// || | +/// || | +/// || | +/// \/ | +/// Parachain(1) ===> Parachain(1) ===> Parachain(1000) +/// ``` +#[test] +fn sending_to_sibling_of_bridged_chain_works() { + let msg = Xcm(vec![Trap(1)]); + let dest = (Parent, Remote::get(), Parachain(1000)).into(); + assert_eq!(send_xcm::(dest, msg).unwrap().1, MultiAssets::new()); + assert_eq!(TheBridge::service(), 1); + let expected = vec![( + (Parent, Parachain(1000)).into(), + Xcm(vec![UniversalOrigin(Local::get().into()), Trap(1)]), + )]; + assert_eq!(take_received_remote_messages(), expected); +} + +/// ```nocompile +/// local | remote +/// | +/// GlobalConsensus(Local::get()) | GlobalConsensus(Remote::get()) +/// || | /\ +/// || | || +/// || | || +/// \/ | || +/// Parachain(1) ===> Parachain(1) +/// ``` +#[test] +fn sending_to_relay_of_bridged_chain_works() { + let msg = Xcm(vec![Trap(1)]); + let dest = (Parent, Remote::get()).into(); + assert_eq!(send_xcm::(dest, msg).unwrap().1, MultiAssets::new()); + assert_eq!(TheBridge::service(), 1); + let expected = vec![(Parent.into(), Xcm(vec![UniversalOrigin(Local::get().into()), Trap(1)]))]; + assert_eq!(take_received_remote_messages(), expected); +} diff --git a/xcm/xcm-builder/src/tests/bridging/remote_relay_relay.rs b/xcm/xcm-builder/src/tests/bridging/remote_relay_relay.rs new file mode 100644 index 000000000000..83cc0ac78096 --- /dev/null +++ b/xcm/xcm-builder/src/tests/bridging/remote_relay_relay.rs @@ -0,0 +1,95 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! This test is when we're sending an XCM from a parachain whose relay-chain hosts a bridge to +//! another relay-chain. The destination of the XCM is within the global consensus of the +//! remote side of the bridge. + +use super::*; + +parameter_types! { + pub UniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(1000)); + pub RelayUniversalLocation: Junctions = X1(GlobalConsensus(Local::get())); + pub RemoteUniversalLocation: Junctions = X1(GlobalConsensus(Remote::get())); + pub BridgeTable: Vec<(NetworkId, MultiLocation, Option)> + = vec![(Remote::get(), MultiLocation::parent(), None)]; +} +type TheBridge = + TestBridge>; +type RelayExporter = HaulBlobExporter; +type LocalInnerRouter = + UnpaidExecutingRouter; +type LocalBridgeRouter = + UnpaidRemoteExporter, LocalInnerRouter, UniversalLocation>; +type LocalRouter = (LocalInnerRouter, LocalBridgeRouter); + +/// ```nocompile +/// local | remote +/// | +/// GlobalConsensus(Local::get()) ========> GlobalConsensus(Remote::get()) +/// /\ | +/// || | +/// || | +/// || | +/// Parachain(1000) | +/// ``` +#[test] +fn sending_to_bridged_chain_works() { + let msg = Xcm(vec![Trap(1)]); + assert_eq!( + send_xcm::((Parent, Parent, Remote::get()).into(), msg).unwrap().1, + MultiAssets::new() + ); + assert_eq!(TheBridge::service(), 1); + assert_eq!( + take_received_remote_messages(), + vec![( + Here.into(), + Xcm(vec![ + UniversalOrigin(Local::get().into()), + DescendOrigin(Parachain(1000).into()), + Trap(1) + ]) + )] + ); +} + +/// ```nocompile +/// local | remote +/// | +/// GlobalConsensus(Local::get()) ========> GlobalConsensus(Remote::get()) +/// /\ | || +/// || | || +/// || | || +/// || | \/ +/// Parachain(1000) | Parachain(1000) +/// ``` +#[test] +fn sending_to_parachain_of_bridged_chain_works() { + let msg = Xcm(vec![Trap(1)]); + let dest = (Parent, Parent, Remote::get(), Parachain(1000)).into(); + assert_eq!(send_xcm::(dest, msg).unwrap().1, MultiAssets::new()); + assert_eq!(TheBridge::service(), 1); + let expected = vec![( + Parachain(1000).into(), + Xcm(vec![ + UniversalOrigin(Local::get().into()), + DescendOrigin(Parachain(1000).into()), + Trap(1), + ]), + )]; + assert_eq!(take_received_remote_messages(), expected); +} diff --git a/xcm/xcm-builder/src/tests/expecting.rs b/xcm/xcm-builder/src/tests/expecting.rs new file mode 100644 index 000000000000..b93b73224dcc --- /dev/null +++ b/xcm/xcm-builder/src/tests/expecting.rs @@ -0,0 +1,187 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use super::*; + +#[test] +fn expect_pallet_should_work() { + AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + // They want to transfer 100 of our native asset from sovereign account of parachain #1 into #2 + // and let them know to hand it to account #3. + let message = Xcm(vec![ExpectPallet { + index: 1, + name: b"Balances".as_ref().into(), + module_name: b"pallet_balances".as_ref().into(), + crate_major: 1, + min_crate_minor: 42, + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + + let message = Xcm(vec![ExpectPallet { + index: 1, + name: b"Balances".as_ref().into(), + module_name: b"pallet_balances".as_ref().into(), + crate_major: 1, + min_crate_minor: 41, + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); +} + +#[test] +fn expect_pallet_should_fail_correctly() { + AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + let message = Xcm(vec![ExpectPallet { + index: 1, + name: b"Balances".as_ref().into(), + module_name: b"pallet_balances".as_ref().into(), + crate_major: 1, + min_crate_minor: 60, + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::VersionIncompatible)); + + let message = Xcm(vec![ExpectPallet { + index: 1, + name: b"System".as_ref().into(), + module_name: b"pallet_balances".as_ref().into(), + crate_major: 1, + min_crate_minor: 42, + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NameMismatch)); + + let message = Xcm(vec![ExpectPallet { + index: 1, + name: b"Balances".as_ref().into(), + module_name: b"pallet_system".as_ref().into(), + crate_major: 1, + min_crate_minor: 42, + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NameMismatch)); + + let message = Xcm(vec![ExpectPallet { + index: 0, + name: b"Balances".as_ref().into(), + module_name: b"pallet_balances".as_ref().into(), + crate_major: 1, + min_crate_minor: 42, + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NameMismatch)); + + let message = Xcm(vec![ExpectPallet { + index: 2, + name: b"Balances".as_ref().into(), + module_name: b"pallet_balances".as_ref().into(), + crate_major: 1, + min_crate_minor: 42, + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::PalletNotFound)); + + let message = Xcm(vec![ExpectPallet { + index: 1, + name: b"Balances".as_ref().into(), + module_name: b"pallet_balances".as_ref().into(), + crate_major: 2, + min_crate_minor: 42, + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::VersionIncompatible)); + + let message = Xcm(vec![ExpectPallet { + index: 1, + name: b"Balances".as_ref().into(), + module_name: b"pallet_balances".as_ref().into(), + crate_major: 0, + min_crate_minor: 42, + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::VersionIncompatible)); + + let message = Xcm(vec![ExpectPallet { + index: 1, + name: b"Balances".as_ref().into(), + module_name: b"pallet_balances".as_ref().into(), + crate_major: 1, + min_crate_minor: 43, + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::VersionIncompatible)); +} diff --git a/xcm/xcm-builder/src/tests/locking.rs b/xcm/xcm-builder/src/tests/locking.rs new file mode 100644 index 000000000000..f226c1bb5766 --- /dev/null +++ b/xcm/xcm-builder/src/tests/locking.rs @@ -0,0 +1,234 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use super::*; +use LockTraceItem::*; + +#[test] +fn lock_roundtrip_should_work() { + // Account #3 and Parachain #1 can execute for free + AllowUnpaidFrom::set(vec![(3u64,).into(), (Parent, Parachain(1)).into()]); + // Account #3 owns 1000 native parent tokens. + add_asset((3u64,), (Parent, 1000u128)); + // Sending a message costs 10 parent-tokens. + set_send_price((Parent, 10u128)); + + // They want to lock 100 of the native parent tokens to be unlocked only by Parachain #1. + let message = Xcm(vec![ + WithdrawAsset((Parent, 100u128).into()), + SetAppendix( + vec![DepositAsset { assets: AllCounted(2).into(), beneficiary: (3u64,).into() }].into(), + ), + LockAsset { asset: (Parent, 100u128).into(), unlocker: (Parent, Parachain(1)).into() }, + ]); + let hash = fake_message_hash(&message); + let r = + XcmExecutor::::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50)); + assert_eq!(r, Outcome::Complete(Weight::from_parts(40, 40))); + assert_eq!(asset_list((3u64,)), vec![(Parent, 990u128).into()]); + + let expected_msg = Xcm::<()>(vec![NoteUnlockable { + owner: (Parent, Parachain(42), 3u64).into(), + asset: (Parent, 100u128).into(), + }]); + let expected_hash = fake_message_hash(&expected_msg); + assert_eq!(sent_xcm(), vec![((Parent, Parachain(1)).into(), expected_msg, expected_hash)]); + assert_eq!( + take_lock_trace(), + vec![Lock { + asset: (Parent, 100u128).into(), + owner: (3u64,).into(), + unlocker: (Parent, Parachain(1)).into(), + }] + ); + + // Now we'll unlock it. + let message = + Xcm(vec![UnlockAsset { asset: (Parent, 100u128).into(), target: (3u64,).into() }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + (Parent, Parachain(1)), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); +} + +#[test] +fn auto_fee_paying_should_work() { + // Account #3 and Parachain #1 can execute for free + AllowUnpaidFrom::set(vec![(3u64,).into()]); + // Account #3 owns 1000 native parent tokens. + add_asset((3u64,), (Parent, 1000u128)); + // Sending a message costs 10 parent-tokens. + set_send_price((Parent, 10u128)); + + // They want to lock 100 of the native parent tokens to be unlocked only by Parachain #1. + let message = Xcm(vec![ + SetFeesMode { jit_withdraw: true }, + LockAsset { asset: (Parent, 100u128).into(), unlocker: (Parent, Parachain(1)).into() }, + ]); + let hash = fake_message_hash(&message); + let r = + XcmExecutor::::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50)); + assert_eq!(r, Outcome::Complete(Weight::from_parts(20, 20))); + assert_eq!(asset_list((3u64,)), vec![(Parent, 990u128).into()]); +} + +#[test] +fn lock_should_fail_correctly() { + // Account #3 can execute for free + AllowUnpaidFrom::set(vec![(3u64,).into(), (Parent, Parachain(1)).into()]); + + // #3 wants to lock 100 of the native parent tokens to be unlocked only by parachain ../#1, + // but they don't have any. + let message = Xcm(vec![LockAsset { + asset: (Parent, 100u128).into(), + unlocker: (Parent, Parachain(1)).into(), + }]); + let hash = fake_message_hash(&message); + let r = + XcmExecutor::::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50)); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::LockError)); + assert_eq!(sent_xcm(), vec![]); + assert_eq!(take_lock_trace(), vec![]); + + // Account #3 owns 1000 native parent tokens. + add_asset((3u64,), (Parent, 1000u128)); + // But we require a price to be paid for the sending + set_send_price((Parent, 10u128)); + + // #3 wants to lock 100 of the native parent tokens to be unlocked only by parachain ../#1, + // but there's nothing to pay the fees for sending the notification message. + let message = Xcm(vec![LockAsset { + asset: (Parent, 100u128).into(), + unlocker: (Parent, Parachain(1)).into(), + }]); + let hash = fake_message_hash(&message); + let r = + XcmExecutor::::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50)); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NotHoldingFees)); + assert_eq!(sent_xcm(), vec![]); + assert_eq!(take_lock_trace(), vec![]); +} + +#[test] +fn remote_unlock_roundtrip_should_work() { + // Account #3 can execute for free + AllowUnpaidFrom::set(vec![(3u64,).into(), (Parent, Parachain(1)).into()]); + // Account #3 owns 1000 native parent tokens. + add_asset((3u64,), (Parent, 1000u128)); + // Sending a message costs 10 parent-tokens. + set_send_price((Parent, 10u128)); + + // We have been told by Parachain #1 that Account #3 has locked funds which we can unlock. + let message = + Xcm(vec![NoteUnlockable { asset: (Parent, 100u128).into(), owner: (3u64,).into() }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + (Parent, Parachain(1)), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + assert_eq!( + take_lock_trace(), + vec![Note { + asset: (Parent, 100u128).into(), + owner: (3u64,).into(), + locker: (Parent, Parachain(1)).into(), + }] + ); + + // Let's request those funds be unlocked. + let message = Xcm(vec![ + WithdrawAsset((Parent, 100u128).into()), + SetAppendix( + vec![DepositAsset { assets: AllCounted(2).into(), beneficiary: (3u64,).into() }].into(), + ), + RequestUnlock { asset: (Parent, 100u128).into(), locker: (Parent, Parachain(1)).into() }, + ]); + let hash = fake_message_hash(&message); + let r = + XcmExecutor::::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50)); + assert_eq!(r, Outcome::Complete(Weight::from_parts(40, 40))); + assert_eq!(asset_list((3u64,)), vec![(Parent, 990u128).into()]); + + let expected_msg = + Xcm::<()>(vec![UnlockAsset { target: (3u64,).into(), asset: (Parent, 100u128).into() }]); + let expected_hash = fake_message_hash(&expected_msg); + assert_eq!(sent_xcm(), vec![((Parent, Parachain(1)).into(), expected_msg, expected_hash)]); + assert_eq!( + take_lock_trace(), + vec![Reduce { + asset: (Parent, 100u128).into(), + owner: (3u64,).into(), + locker: (Parent, Parachain(1)).into(), + }] + ); +} + +#[test] +fn remote_unlock_should_fail_correctly() { + // Account #3 can execute for free + AllowUnpaidFrom::set(vec![(3u64,).into(), (Parent, Parachain(1)).into()]); + // But we require a price to be paid for the sending + set_send_price((Parent, 10u128)); + + // We want to unlock 100 of the native parent tokens which were locked for us on parachain. + // This won't work as we don't have any record of them being locked for us. + // No message will be sent and no lock records changed. + let message = Xcm(vec![RequestUnlock { + asset: (Parent, 100u128).into(), + locker: (Parent, Parachain(1)).into(), + }]); + let hash = fake_message_hash(&message); + let r = + XcmExecutor::::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50)); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::LockError)); + assert_eq!(sent_xcm(), vec![]); + assert_eq!(take_lock_trace(), vec![]); + + // We have been told by Parachain #1 that Account #3 has locked funds which we can unlock. + let message = + Xcm(vec![NoteUnlockable { asset: (Parent, 100u128).into(), owner: (3u64,).into() }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + (Parent, Parachain(1)), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + let _discard = take_lock_trace(); + + // We want to unlock 100 of the native parent tokens which were locked for us on parachain. + // This won't work now as we don't have the funds to send the onward message. + // No message will be sent and no lock records changed. + let message = Xcm(vec![RequestUnlock { + asset: (Parent, 100u128).into(), + locker: (Parent, Parachain(1)).into(), + }]); + let hash = fake_message_hash(&message); + let r = + XcmExecutor::::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50)); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NotHoldingFees)); + + assert_eq!(sent_xcm(), vec![]); + assert_eq!(take_lock_trace(), vec![]); +} diff --git a/xcm/xcm-builder/src/tests/mock.rs b/xcm/xcm-builder/src/tests/mock.rs new file mode 100644 index 000000000000..fa85d09a443e --- /dev/null +++ b/xcm/xcm-builder/src/tests/mock.rs @@ -0,0 +1,656 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use crate::{barriers::AllowSubscriptionsFrom, test_utils::*}; +pub use crate::{ + AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, + AllowUnpaidExecutionFrom, FixedRateOfFungible, FixedWeightBounds, TakeWeightCredit, +}; +use frame_support::traits::{ContainsPair, Everything}; +pub use frame_support::{ + dispatch::{ + DispatchError, DispatchInfo, DispatchResultWithPostInfo, Dispatchable, GetDispatchInfo, + Parameter, PostDispatchInfo, + }, + ensure, parameter_types, + sp_runtime::DispatchErrorWithPostInfo, + traits::{Contains, Get, IsInVec}, +}; +pub use parity_scale_codec::{Decode, Encode}; +pub use sp_io::hashing::blake2_256; +pub use sp_std::{ + cell::RefCell, + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + fmt::Debug, + marker::PhantomData, +}; +pub use xcm::latest::{prelude::*, Weight}; +pub use xcm_executor::{ + traits::{ + AssetExchange, AssetLock, ConvertOrigin, Enact, ExportXcm, FeeManager, FeeReason, + LockError, OnResponse, TransactAsset, + }, + Assets, Config, +}; + +pub enum TestOrigin { + Root, + Relay, + Signed(u64), + Parachain(u32), +} + +/// A dummy call. +/// +/// Each item contains the amount of weight that it *wants* to consume as the first item, and the actual amount (if +/// different from the former) in the second option. +#[derive(Debug, Encode, Decode, Eq, PartialEq, Clone, Copy, scale_info::TypeInfo)] +pub enum TestCall { + OnlyRoot(Weight, Option), + OnlyParachain(Weight, Option, Option), + OnlySigned(Weight, Option, Option), + Any(Weight, Option), +} +impl Dispatchable for TestCall { + type RuntimeOrigin = TestOrigin; + type Config = (); + type Info = (); + type PostInfo = PostDispatchInfo; + fn dispatch(self, origin: Self::RuntimeOrigin) -> DispatchResultWithPostInfo { + let mut post_info = PostDispatchInfo::default(); + let maybe_actual = match self { + TestCall::OnlyRoot(_, maybe_actual) | + TestCall::OnlySigned(_, maybe_actual, _) | + TestCall::OnlyParachain(_, maybe_actual, _) | + TestCall::Any(_, maybe_actual) => maybe_actual, + }; + post_info.actual_weight = maybe_actual; + if match (&origin, &self) { + (TestOrigin::Parachain(i), TestCall::OnlyParachain(_, _, Some(j))) => i == j, + (TestOrigin::Signed(i), TestCall::OnlySigned(_, _, Some(j))) => i == j, + (TestOrigin::Root, TestCall::OnlyRoot(..)) | + (TestOrigin::Parachain(_), TestCall::OnlyParachain(_, _, None)) | + (TestOrigin::Signed(_), TestCall::OnlySigned(_, _, None)) | + (_, TestCall::Any(..)) => true, + _ => false, + } { + Ok(post_info) + } else { + Err(DispatchErrorWithPostInfo { error: DispatchError::BadOrigin, post_info }) + } + } +} + +impl GetDispatchInfo for TestCall { + fn get_dispatch_info(&self) -> DispatchInfo { + let weight = *match self { + TestCall::OnlyRoot(estimate, ..) | + TestCall::OnlyParachain(estimate, ..) | + TestCall::OnlySigned(estimate, ..) | + TestCall::Any(estimate, ..) => estimate, + }; + DispatchInfo { weight, ..Default::default() } + } +} + +thread_local! { + pub static SENT_XCM: RefCell, XcmHash)>> = RefCell::new(Vec::new()); + pub static EXPORTED_XCM: RefCell< + Vec<(NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, XcmHash)> + > = RefCell::new(Vec::new()); + pub static EXPORTER_OVERRIDE: RefCell, + ) -> Result, + fn( + NetworkId, + u32, + InteriorMultiLocation, + InteriorMultiLocation, + Xcm<()>, + ) -> Result, + )>> = RefCell::new(None); + pub static SEND_PRICE: RefCell = RefCell::new(MultiAssets::new()); +} +pub fn sent_xcm() -> Vec<(MultiLocation, opaque::Xcm, XcmHash)> { + SENT_XCM.with(|q| (*q.borrow()).clone()) +} +pub fn set_send_price(p: impl Into) { + SEND_PRICE.with(|l| l.replace(p.into().into())); +} +pub fn exported_xcm( +) -> Vec<(NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, opaque::Xcm, XcmHash)> { + EXPORTED_XCM.with(|q| (*q.borrow()).clone()) +} +pub fn set_exporter_override( + price: fn( + NetworkId, + u32, + &InteriorMultiLocation, + &InteriorMultiLocation, + &Xcm<()>, + ) -> Result, + deliver: fn( + NetworkId, + u32, + InteriorMultiLocation, + InteriorMultiLocation, + Xcm<()>, + ) -> Result, +) { + EXPORTER_OVERRIDE.with(|x| x.replace(Some((price, deliver)))); +} +#[allow(dead_code)] +pub fn clear_exporter_override() { + EXPORTER_OVERRIDE.with(|x| x.replace(None)); +} +pub struct TestMessageSender; +impl SendXcm for TestMessageSender { + type Ticket = (MultiLocation, Xcm<()>, XcmHash); + fn validate( + dest: &mut Option, + msg: &mut Option>, + ) -> SendResult<(MultiLocation, Xcm<()>, XcmHash)> { + let msg = msg.take().unwrap(); + let hash = fake_message_hash(&msg); + let triplet = (dest.take().unwrap(), msg, hash); + Ok((triplet, SEND_PRICE.with(|l| l.borrow().clone()))) + } + fn deliver(triplet: (MultiLocation, Xcm<()>, XcmHash)) -> Result { + let hash = triplet.2; + SENT_XCM.with(|q| q.borrow_mut().push(triplet)); + Ok(hash) + } +} +pub struct TestMessageExporter; +impl ExportXcm for TestMessageExporter { + type Ticket = (NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, XcmHash); + fn validate( + network: NetworkId, + channel: u32, + uni_src: &mut Option, + dest: &mut Option, + msg: &mut Option>, + ) -> SendResult<(NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, XcmHash)> + { + let (s, d, m) = (uni_src.take().unwrap(), dest.take().unwrap(), msg.take().unwrap()); + let r: Result = EXPORTER_OVERRIDE.with(|e| { + if let Some((ref f, _)) = &*e.borrow() { + f(network, channel, &s, &d, &m) + } else { + Ok(MultiAssets::new()) + } + }); + let h = fake_message_hash(&m); + match r { + Ok(price) => Ok(((network, channel, s, d, m, h), price)), + Err(e) => { + *uni_src = Some(s); + *dest = Some(d); + *msg = Some(m); + Err(e) + }, + } + } + fn deliver( + tuple: (NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, XcmHash), + ) -> Result { + EXPORTER_OVERRIDE.with(|e| { + if let Some((_, ref f)) = &*e.borrow() { + let (network, channel, uni_src, dest, msg, _hash) = tuple; + f(network, channel, uni_src, dest, msg) + } else { + let hash = tuple.5; + EXPORTED_XCM.with(|q| q.borrow_mut().push(tuple)); + Ok(hash) + } + }) + } +} + +thread_local! { + pub static ASSETS: RefCell> = RefCell::new(BTreeMap::new()); +} +pub fn assets(who: impl Into) -> Assets { + ASSETS.with(|a| a.borrow().get(&who.into()).cloned()).unwrap_or_default() +} +pub fn asset_list(who: impl Into) -> Vec { + MultiAssets::from(assets(who)).into_inner() +} +pub fn add_asset(who: impl Into, what: impl Into) { + ASSETS.with(|a| a.borrow_mut().entry(who.into()).or_insert(Assets::new()).subsume(what.into())); +} + +pub struct TestAssetTransactor; +impl TransactAsset for TestAssetTransactor { + fn deposit_asset( + what: &MultiAsset, + who: &MultiLocation, + _context: &XcmContext, + ) -> Result<(), XcmError> { + add_asset(who.clone(), what.clone()); + Ok(()) + } + + fn withdraw_asset( + what: &MultiAsset, + who: &MultiLocation, + _maybe_context: Option<&XcmContext>, + ) -> Result { + ASSETS.with(|a| { + a.borrow_mut() + .get_mut(who) + .ok_or(XcmError::NotWithdrawable)? + .try_take(what.clone().into()) + .map_err(|_| XcmError::NotWithdrawable) + }) + } +} + +pub fn to_account(l: impl Into) -> Result { + Ok(match l.into() { + // Siblings at 2000+id + MultiLocation { parents: 1, interior: X1(Parachain(id)) } => 2000 + id as u64, + // Accounts are their number + MultiLocation { parents: 0, interior: X1(AccountIndex64 { index, .. }) } => index, + // Children at 1000+id + MultiLocation { parents: 0, interior: X1(Parachain(id)) } => 1000 + id as u64, + // Self at 3000 + MultiLocation { parents: 0, interior: Here } => 3000, + // Parent at 3001 + MultiLocation { parents: 1, interior: Here } => 3001, + l => { + // Is it a foreign-consensus? + let uni = ExecutorUniversalLocation::get(); + if l.parents as usize != uni.len() { + return Err(l) + } + match l.first_interior() { + Some(GlobalConsensus(Kusama)) => 4000, + Some(GlobalConsensus(Polkadot)) => 4001, + _ => return Err(l), + } + }, + }) +} + +pub struct TestOriginConverter; +impl ConvertOrigin for TestOriginConverter { + fn convert_origin( + origin: impl Into, + kind: OriginKind, + ) -> Result { + use OriginKind::*; + match (kind, origin.into()) { + (Superuser, _) => Ok(TestOrigin::Root), + (SovereignAccount, l) => Ok(TestOrigin::Signed(to_account(l)?)), + (Native, MultiLocation { parents: 0, interior: X1(Parachain(id)) }) => + Ok(TestOrigin::Parachain(id)), + (Native, MultiLocation { parents: 1, interior: Here }) => Ok(TestOrigin::Relay), + (Native, MultiLocation { parents: 0, interior: X1(AccountIndex64 { index, .. }) }) => + Ok(TestOrigin::Signed(index)), + (_, origin) => Err(origin), + } + } +} + +thread_local! { + pub static IS_RESERVE: RefCell>> = RefCell::new(BTreeMap::new()); + pub static IS_TELEPORTER: RefCell>> = RefCell::new(BTreeMap::new()); + pub static UNIVERSAL_ALIASES: RefCell> = RefCell::new(BTreeSet::new()); +} +pub fn add_reserve(from: MultiLocation, asset: MultiAssetFilter) { + IS_RESERVE.with(|r| r.borrow_mut().entry(from).or_default().push(asset)); +} +#[allow(dead_code)] +pub fn add_teleporter(from: MultiLocation, asset: MultiAssetFilter) { + IS_TELEPORTER.with(|r| r.borrow_mut().entry(from).or_default().push(asset)); +} +pub fn add_universal_alias(bridge: impl Into, consensus: impl Into) { + UNIVERSAL_ALIASES.with(|r| r.borrow_mut().insert((bridge.into(), consensus.into()))); +} +pub fn clear_universal_aliases() { + UNIVERSAL_ALIASES.with(|r| r.replace(Default::default())); +} + +pub struct TestIsReserve; +impl ContainsPair for TestIsReserve { + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + IS_RESERVE + .with(|r| r.borrow().get(origin).map_or(false, |v| v.iter().any(|a| a.matches(asset)))) + } +} +pub struct TestIsTeleporter; +impl ContainsPair for TestIsTeleporter { + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + IS_TELEPORTER + .with(|r| r.borrow().get(origin).map_or(false, |v| v.iter().any(|a| a.matches(asset)))) + } +} + +pub struct TestUniversalAliases; +impl Contains<(MultiLocation, Junction)> for TestUniversalAliases { + fn contains(t: &(MultiLocation, Junction)) -> bool { + UNIVERSAL_ALIASES.with(|r| r.borrow().contains(t)) + } +} + +pub enum ResponseSlot { + Expecting(MultiLocation), + Received(Response), +} +thread_local! { + pub static QUERIES: RefCell> = RefCell::new(BTreeMap::new()); +} +pub struct TestResponseHandler; +impl OnResponse for TestResponseHandler { + fn expecting_response( + origin: &MultiLocation, + query_id: u64, + _querier: Option<&MultiLocation>, + ) -> bool { + QUERIES.with(|q| match q.borrow().get(&query_id) { + Some(ResponseSlot::Expecting(ref l)) => l == origin, + _ => false, + }) + } + fn on_response( + _origin: &MultiLocation, + query_id: u64, + _querier: Option<&MultiLocation>, + response: xcm::latest::Response, + _max_weight: Weight, + _context: &XcmContext, + ) -> Weight { + QUERIES.with(|q| { + q.borrow_mut().entry(query_id).and_modify(|v| { + if matches!(*v, ResponseSlot::Expecting(..)) { + *v = ResponseSlot::Received(response); + } + }); + }); + Weight::from_parts(10, 10) + } +} +pub fn expect_response(query_id: u64, from: MultiLocation) { + QUERIES.with(|q| q.borrow_mut().insert(query_id, ResponseSlot::Expecting(from))); +} +pub fn response(query_id: u64) -> Option { + QUERIES.with(|q| { + q.borrow().get(&query_id).and_then(|v| match v { + ResponseSlot::Received(r) => Some(r.clone()), + _ => None, + }) + }) +} + +parameter_types! { + pub static ExecutorUniversalLocation: InteriorMultiLocation + = (ByGenesis([0; 32]), Parachain(42)).into(); + pub UnitWeightCost: Weight = Weight::from_parts(10, 10); +} +parameter_types! { + // Nothing is allowed to be paid/unpaid by default. + pub static AllowExplicitUnpaidFrom: Vec = vec![]; + pub static AllowUnpaidFrom: Vec = vec![]; + pub static AllowPaidFrom: Vec = vec![]; + pub static AllowSubsFrom: Vec = vec![]; + // 1_000_000_000_000 => 1 unit of asset for 1 unit of ref time weight. + // 1024 * 1024 => 1 unit of asset for 1 unit of proof size weight. + pub static WeightPrice: (AssetId, u128, u128) = + (From::from(Here), 1_000_000_000_000, 1024 * 1024); + pub static MaxInstructions: u32 = 100; +} + +pub type TestBarrier = ( + TakeWeightCredit, + AllowKnownQueryResponses, + AllowTopLevelPaidExecutionFrom>, + AllowExplicitUnpaidExecutionFrom>, + AllowUnpaidExecutionFrom>, + AllowSubscriptionsFrom>, +); + +thread_local! { + pub static IS_WAIVED: RefCell> = RefCell::new(vec![]); +} +#[allow(dead_code)] +pub fn set_fee_waiver(waived: Vec) { + IS_WAIVED.with(|l| l.replace(waived)); +} + +pub struct TestFeeManager; +impl FeeManager for TestFeeManager { + fn is_waived(_: Option<&MultiLocation>, r: FeeReason) -> bool { + IS_WAIVED.with(|l| l.borrow().contains(&r)) + } + fn handle_fee(_: MultiAssets) {} +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum LockTraceItem { + Lock { unlocker: MultiLocation, asset: MultiAsset, owner: MultiLocation }, + Unlock { unlocker: MultiLocation, asset: MultiAsset, owner: MultiLocation }, + Note { locker: MultiLocation, asset: MultiAsset, owner: MultiLocation }, + Reduce { locker: MultiLocation, asset: MultiAsset, owner: MultiLocation }, +} +thread_local! { + pub static NEXT_INDEX: RefCell = RefCell::new(0); + pub static LOCK_TRACE: RefCell> = RefCell::new(Vec::new()); + pub static ALLOWED_UNLOCKS: RefCell> = RefCell::new(BTreeMap::new()); + pub static ALLOWED_REQUEST_UNLOCKS: RefCell> = RefCell::new(BTreeMap::new()); +} + +pub fn take_lock_trace() -> Vec { + LOCK_TRACE.with(|l| l.replace(Vec::new())) +} +pub fn allow_unlock( + unlocker: impl Into, + asset: impl Into, + owner: impl Into, +) { + ALLOWED_UNLOCKS.with(|l| { + l.borrow_mut() + .entry((owner.into(), unlocker.into())) + .or_default() + .subsume(asset.into()) + }); +} +pub fn disallow_unlock( + unlocker: impl Into, + asset: impl Into, + owner: impl Into, +) { + ALLOWED_UNLOCKS.with(|l| { + l.borrow_mut() + .entry((owner.into(), unlocker.into())) + .or_default() + .saturating_take(asset.into().into()) + }); +} +pub fn unlock_allowed(unlocker: &MultiLocation, asset: &MultiAsset, owner: &MultiLocation) -> bool { + ALLOWED_UNLOCKS.with(|l| { + l.borrow_mut() + .get(&(owner.clone(), unlocker.clone())) + .map_or(false, |x| x.contains_asset(asset)) + }) +} +pub fn allow_request_unlock( + locker: impl Into, + asset: impl Into, + owner: impl Into, +) { + ALLOWED_REQUEST_UNLOCKS.with(|l| { + l.borrow_mut() + .entry((owner.into(), locker.into())) + .or_default() + .subsume(asset.into()) + }); +} +pub fn disallow_request_unlock( + locker: impl Into, + asset: impl Into, + owner: impl Into, +) { + ALLOWED_REQUEST_UNLOCKS.with(|l| { + l.borrow_mut() + .entry((owner.into(), locker.into())) + .or_default() + .saturating_take(asset.into().into()) + }); +} +pub fn request_unlock_allowed( + locker: &MultiLocation, + asset: &MultiAsset, + owner: &MultiLocation, +) -> bool { + ALLOWED_REQUEST_UNLOCKS.with(|l| { + l.borrow_mut() + .get(&(owner.clone(), locker.clone())) + .map_or(false, |x| x.contains_asset(asset)) + }) +} + +pub struct TestTicket(LockTraceItem); +impl Enact for TestTicket { + fn enact(self) -> Result<(), LockError> { + match &self.0 { + LockTraceItem::Lock { unlocker, asset, owner } => + allow_unlock(unlocker.clone(), asset.clone(), owner.clone()), + LockTraceItem::Unlock { unlocker, asset, owner } => + disallow_unlock(unlocker.clone(), asset.clone(), owner.clone()), + LockTraceItem::Reduce { locker, asset, owner } => + disallow_request_unlock(locker.clone(), asset.clone(), owner.clone()), + _ => {}, + } + LOCK_TRACE.with(move |l| l.borrow_mut().push(self.0)); + Ok(()) + } +} + +pub struct TestAssetLock; +impl AssetLock for TestAssetLock { + type LockTicket = TestTicket; + type UnlockTicket = TestTicket; + type ReduceTicket = TestTicket; + + fn prepare_lock( + unlocker: MultiLocation, + asset: MultiAsset, + owner: MultiLocation, + ) -> Result { + ensure!(assets(owner.clone()).contains_asset(&asset), LockError::AssetNotOwned); + Ok(TestTicket(LockTraceItem::Lock { unlocker, asset, owner })) + } + + fn prepare_unlock( + unlocker: MultiLocation, + asset: MultiAsset, + owner: MultiLocation, + ) -> Result { + ensure!(unlock_allowed(&unlocker, &asset, &owner), LockError::NotLocked); + Ok(TestTicket(LockTraceItem::Unlock { unlocker, asset, owner })) + } + + fn note_unlockable( + locker: MultiLocation, + asset: MultiAsset, + owner: MultiLocation, + ) -> Result<(), LockError> { + allow_request_unlock(locker.clone(), asset.clone(), owner.clone()); + let item = LockTraceItem::Note { locker, asset, owner }; + LOCK_TRACE.with(move |l| l.borrow_mut().push(item)); + Ok(()) + } + + fn prepare_reduce_unlockable( + locker: MultiLocation, + asset: MultiAsset, + owner: MultiLocation, + ) -> Result { + ensure!(request_unlock_allowed(&locker, &asset, &owner), LockError::NotLocked); + Ok(TestTicket(LockTraceItem::Reduce { locker, asset, owner })) + } +} + +thread_local! { + pub static EXCHANGE_ASSETS: RefCell = RefCell::new(Assets::new()); +} +pub fn set_exchange_assets(assets: impl Into) { + EXCHANGE_ASSETS.with(|a| a.replace(assets.into().into())); +} +pub fn exchange_assets() -> MultiAssets { + EXCHANGE_ASSETS.with(|a| a.borrow().clone().into()) +} +pub struct TestAssetExchange; +impl AssetExchange for TestAssetExchange { + fn exchange_asset( + _origin: Option<&MultiLocation>, + give: Assets, + want: &MultiAssets, + maximal: bool, + ) -> Result { + let mut have = EXCHANGE_ASSETS.with(|l| l.borrow().clone()); + ensure!(have.contains_assets(want), give); + let get = if maximal { + std::mem::replace(&mut have, Assets::new()) + } else { + have.saturating_take(want.clone().into()) + }; + have.subsume_assets(give); + EXCHANGE_ASSETS.with(|l| l.replace(have)); + Ok(get) + } +} + +pub struct TestConfig; +impl Config for TestConfig { + type RuntimeCall = TestCall; + type XcmSender = TestMessageSender; + type AssetTransactor = TestAssetTransactor; + type OriginConverter = TestOriginConverter; + type IsReserve = TestIsReserve; + type IsTeleporter = TestIsTeleporter; + type UniversalLocation = ExecutorUniversalLocation; + type Barrier = TestBarrier; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfFungible; + type ResponseHandler = TestResponseHandler; + type AssetTrap = TestAssetTrap; + type AssetLocker = TestAssetLock; + type AssetExchanger = TestAssetExchange; + type AssetClaims = TestAssetTrap; + type SubscriptionService = TestSubscriptionService; + type PalletInstancesInfo = TestPalletsInfo; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = TestFeeManager; + type UniversalAliases = TestUniversalAliases; + type MessageExporter = TestMessageExporter; + type CallDispatcher = TestCall; + type SafeCallFilter = Everything; +} + +pub fn fungible_multi_asset(location: MultiLocation, amount: u128) -> MultiAsset { + (AssetId::from(location), Fungibility::Fungible(amount)).into() +} + +pub fn fake_message_hash(message: &Xcm) -> XcmHash { + message.using_encoded(sp_io::hashing::blake2_256) +} diff --git a/xcm/procedural/src/v0.rs b/xcm/xcm-builder/src/tests/mod.rs similarity index 56% rename from xcm/procedural/src/v0.rs rename to xcm/xcm-builder/src/tests/mod.rs index 7774df4e9f8f..cc83d9d5ac98 100644 --- a/xcm/procedural/src/v0.rs +++ b/xcm/xcm-builder/src/tests/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright 2022 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -14,4 +14,26 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -pub mod multilocation; +use super::{test_utils::*, *}; +use core::convert::TryInto; +use frame_support::{ + assert_err, + traits::{ConstU32, ContainsPair}, + weights::constants::{WEIGHT_PROOF_SIZE_PER_MB, WEIGHT_REF_TIME_PER_SECOND}, +}; +use xcm_executor::{traits::prelude::*, Config, XcmExecutor}; + +mod mock; +use mock::*; + +mod assets; +mod barriers; +mod basic; +mod bridging; +mod expecting; +mod locking; +mod origins; +mod querying; +mod transacting; +mod version_subscriptions; +mod weight; diff --git a/xcm/xcm-builder/src/tests/origins.rs b/xcm/xcm-builder/src/tests/origins.rs new file mode 100644 index 000000000000..a7454a4f423e --- /dev/null +++ b/xcm/xcm-builder/src/tests/origins.rs @@ -0,0 +1,140 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use super::*; + +#[test] +fn universal_origin_should_work() { + AllowUnpaidFrom::set(vec![X1(Parachain(1)).into(), X1(Parachain(2)).into()]); + clear_universal_aliases(); + // Parachain 1 may represent Kusama to us + add_universal_alias(Parachain(1), Kusama); + // Parachain 2 may represent Polkadot to us + add_universal_alias(Parachain(2), Polkadot); + + let message = Xcm(vec![ + UniversalOrigin(GlobalConsensus(Kusama)), + TransferAsset { assets: (Parent, 100u128).into(), beneficiary: Here.into() }, + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(2), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::InvalidLocation)); + + let message = Xcm(vec![ + UniversalOrigin(GlobalConsensus(Kusama)), + TransferAsset { assets: (Parent, 100u128).into(), beneficiary: Here.into() }, + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(20, 20), XcmError::NotWithdrawable)); + + add_asset((Ancestor(2), GlobalConsensus(Kusama)), (Parent, 100)); + let message = Xcm(vec![ + UniversalOrigin(GlobalConsensus(Kusama)), + TransferAsset { assets: (Parent, 100u128).into(), beneficiary: Here.into() }, + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(20, 20))); + assert_eq!(asset_list((Ancestor(2), GlobalConsensus(Kusama))), vec![]); +} + +#[test] +fn export_message_should_work() { + // Bridge chain (assumed to be Relay) lets Parachain #1 have message execution for free. + AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + // Local parachain #1 issues a transfer asset on Polkadot Relay-chain, transfering 100 Planck to + // Polkadot parachain #2. + let expected_message = Xcm(vec![TransferAsset { + assets: (Here, 100u128).into(), + beneficiary: Parachain(2).into(), + }]); + let expected_hash = fake_message_hash(&expected_message); + let message = Xcm(vec![ExportMessage { + network: Polkadot, + destination: Here, + xcm: expected_message.clone(), + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + let uni_src = (ByGenesis([0; 32]), Parachain(42), Parachain(1)).into(); + assert_eq!( + exported_xcm(), + vec![(Polkadot, 403611790, uni_src, Here, expected_message, expected_hash)] + ); +} + +#[test] +fn unpaid_execution_should_work() { + // Bridge chain (assumed to be Relay) lets Parachain #1 have message execution for free. + AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + // Bridge chain (assumed to be Relay) lets Parachain #2 have message execution for free if it + // asks. + AllowExplicitUnpaidFrom::set(vec![X1(Parachain(2)).into()]); + // Asking for unpaid execution of up to 9 weight on the assumption it is origin of #2. + let message = Xcm(vec![UnpaidExecution { + weight_limit: Limited(Weight::from_parts(9, 9)), + check_origin: Some(Parachain(2).into()), + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message.clone(), + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::BadOrigin)); + let r = XcmExecutor::::execute_xcm( + Parachain(2), + message.clone(), + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Error(XcmError::Barrier)); + + let message = Xcm(vec![UnpaidExecution { + weight_limit: Limited(Weight::from_parts(10, 10)), + check_origin: Some(Parachain(2).into()), + }]); + let r = XcmExecutor::::execute_xcm( + Parachain(2), + message.clone(), + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); +} diff --git a/xcm/xcm-builder/src/tests/querying.rs b/xcm/xcm-builder/src/tests/querying.rs new file mode 100644 index 000000000000..014bbe713cf9 --- /dev/null +++ b/xcm/xcm-builder/src/tests/querying.rs @@ -0,0 +1,119 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use super::*; + +#[test] +fn pallet_query_should_work() { + AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + // They want to transfer 100 of our native asset from sovereign account of parachain #1 into #2 + // and let them know to hand it to account #3. + let message = Xcm(vec![QueryPallet { + module_name: "Error".into(), + response_info: QueryResponseInfo { + destination: Parachain(1).into(), + query_id: 1, + max_weight: Weight::from_parts(50, 50), + }, + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + + let expected_msg = Xcm::<()>(vec![QueryResponse { + query_id: 1, + max_weight: Weight::from_parts(50, 50), + response: Response::PalletsInfo(vec![].try_into().unwrap()), + querier: Some(Here.into()), + }]); + let expected_hash = fake_message_hash(&expected_msg); + assert_eq!(sent_xcm(), vec![(Parachain(1).into(), expected_msg, expected_hash)]); +} + +#[test] +fn pallet_query_with_results_should_work() { + AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + // They want to transfer 100 of our native asset from sovereign account of parachain #1 into #2 + // and let them know to hand it to account #3. + let message = Xcm(vec![QueryPallet { + module_name: "pallet_balances".into(), + response_info: QueryResponseInfo { + destination: Parachain(1).into(), + query_id: 1, + max_weight: Weight::from_parts(50, 50), + }, + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm( + Parachain(1), + message, + hash, + Weight::from_parts(50, 50), + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + + let expected_msg = Xcm::<()>(vec![QueryResponse { + query_id: 1, + max_weight: Weight::from_parts(50, 50), + response: Response::PalletsInfo( + vec![PalletInfo::new( + 1, + b"Balances".as_ref().into(), + b"pallet_balances".as_ref().into(), + 1, + 42, + 69, + ) + .unwrap()] + .try_into() + .unwrap(), + ), + querier: Some(Here.into()), + }]); + let expected_hash = fake_message_hash(&expected_msg); + assert_eq!(sent_xcm(), vec![(Parachain(1).into(), expected_msg, expected_hash)]); +} + +#[test] +fn prepaid_result_of_query_should_get_free_execution() { + let query_id = 33; + // We put this in manually here, but normally this would be done at the point of crafting the message. + expect_response(query_id, Parent.into()); + + let the_response = Response::Assets((Parent, 100u128).into()); + let message = Xcm::(vec![QueryResponse { + query_id, + response: the_response.clone(), + max_weight: Weight::from_parts(10, 10), + querier: Some(Here.into()), + }]); + let hash = fake_message_hash(&message); + let weight_limit = Weight::from_parts(10, 10); + + // First time the response gets through since we're expecting it... + let r = XcmExecutor::::execute_xcm(Parent, message.clone(), hash, weight_limit); + assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + assert_eq!(response(query_id).unwrap(), the_response); + + // Second time it doesn't, since we're not. + let r = XcmExecutor::::execute_xcm(Parent, message.clone(), hash, weight_limit); + assert_eq!(r, Outcome::Error(XcmError::Barrier)); +} diff --git a/xcm/xcm-builder/src/tests/transacting.rs b/xcm/xcm-builder/src/tests/transacting.rs new file mode 100644 index 000000000000..bcff05f35d66 --- /dev/null +++ b/xcm/xcm-builder/src/tests/transacting.rs @@ -0,0 +1,188 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use super::*; + +#[test] +fn transacting_should_work() { + AllowUnpaidFrom::set(vec![Parent.into()]); + + let message = Xcm::(vec![Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: Weight::from_parts(50, 50), + call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(), + }]); + let hash = fake_message_hash(&message); + let weight_limit = Weight::from_parts(60, 60); + let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); + assert_eq!(r, Outcome::Complete(Weight::from_parts(60, 60))); +} + +#[test] +fn transacting_should_respect_max_weight_requirement() { + AllowUnpaidFrom::set(vec![Parent.into()]); + + let message = Xcm::(vec![Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: Weight::from_parts(40, 40), + call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(), + }]); + let hash = fake_message_hash(&message); + let weight_limit = Weight::from_parts(60, 60); + let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(50, 50), XcmError::MaxWeightInvalid)); +} + +#[test] +fn transacting_should_refund_weight() { + AllowUnpaidFrom::set(vec![Parent.into()]); + + let message = Xcm::(vec![Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: Weight::from_parts(50, 50), + call: TestCall::Any(Weight::from_parts(50, 50), Some(Weight::from_parts(30, 30))) + .encode() + .into(), + }]); + let hash = fake_message_hash(&message); + let weight_limit = Weight::from_parts(60, 60); + let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); + assert_eq!(r, Outcome::Complete(Weight::from_parts(40, 40))); +} + +#[test] +fn paid_transacting_should_refund_payment_for_unused_weight() { + let one: MultiLocation = AccountIndex64 { index: 1, network: None }.into(); + AllowPaidFrom::set(vec![one.clone()]); + add_asset(AccountIndex64 { index: 1, network: None }, (Parent, 200u128)); + WeightPrice::set((Parent.into(), 1_000_000_000_000, 1024 * 1024)); + + let origin = one.clone(); + let fees = (Parent, 200u128).into(); + let message = Xcm::(vec![ + WithdrawAsset((Parent, 200u128).into()), // enough for 200 units of weight. + BuyExecution { fees, weight_limit: Limited(Weight::from_parts(100, 100)) }, + Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: Weight::from_parts(50, 50), + // call estimated at 50 but only takes 10. + call: TestCall::Any(Weight::from_parts(50, 50), Some(Weight::from_parts(10, 10))) + .encode() + .into(), + }, + RefundSurplus, + DepositAsset { assets: AllCounted(1).into(), beneficiary: one.clone() }, + ]); + let hash = fake_message_hash(&message); + let weight_limit = Weight::from_parts(100, 100); + let r = XcmExecutor::::execute_xcm(origin, message, hash, weight_limit); + assert_eq!(r, Outcome::Complete(Weight::from_parts(60, 60))); + assert_eq!( + asset_list(AccountIndex64 { index: 1, network: None }), + vec![(Parent, 80u128).into()] + ); +} + +#[test] +fn report_successful_transact_status_should_work() { + AllowUnpaidFrom::set(vec![Parent.into()]); + + let message = Xcm::(vec![ + Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: Weight::from_parts(50, 50), + call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(), + }, + ReportTransactStatus(QueryResponseInfo { + destination: Parent.into(), + query_id: 42, + max_weight: Weight::from_parts(5000, 5000), + }), + ]); + let hash = fake_message_hash(&message); + let weight_limit = Weight::from_parts(70, 70); + let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); + assert_eq!(r, Outcome::Complete(Weight::from_parts(70, 70))); + let expected_msg = Xcm(vec![QueryResponse { + response: Response::DispatchResult(MaybeErrorCode::Success), + query_id: 42, + max_weight: Weight::from_parts(5000, 5000), + querier: Some(Here.into()), + }]); + let expected_hash = fake_message_hash(&expected_msg); + assert_eq!(sent_xcm(), vec![(Parent.into(), expected_msg, expected_hash)]); +} + +#[test] +fn report_failed_transact_status_should_work() { + AllowUnpaidFrom::set(vec![Parent.into()]); + + let message = Xcm::(vec![ + Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: Weight::from_parts(50, 50), + call: TestCall::OnlyRoot(Weight::from_parts(50, 50), None).encode().into(), + }, + ReportTransactStatus(QueryResponseInfo { + destination: Parent.into(), + query_id: 42, + max_weight: Weight::from_parts(5000, 5000), + }), + ]); + let hash = fake_message_hash(&message); + let weight_limit = Weight::from_parts(70, 70); + let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); + assert_eq!(r, Outcome::Complete(Weight::from_parts(70, 70))); + let expected_msg = Xcm(vec![QueryResponse { + response: Response::DispatchResult(MaybeErrorCode::Error(vec![2])), + query_id: 42, + max_weight: Weight::from_parts(5000, 5000), + querier: Some(Here.into()), + }]); + let expected_hash = fake_message_hash(&expected_msg); + assert_eq!(sent_xcm(), vec![(Parent.into(), expected_msg, expected_hash)]); +} + +#[test] +fn clear_transact_status_should_work() { + AllowUnpaidFrom::set(vec![Parent.into()]); + + let message = Xcm::(vec![ + Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: Weight::from_parts(50, 50), + call: TestCall::OnlyRoot(Weight::from_parts(50, 50), None).encode().into(), + }, + ClearTransactStatus, + ReportTransactStatus(QueryResponseInfo { + destination: Parent.into(), + query_id: 42, + max_weight: Weight::from_parts(5000, 5000), + }), + ]); + let hash = fake_message_hash(&message); + let weight_limit = Weight::from_parts(80, 80); + let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); + assert_eq!(r, Outcome::Complete(Weight::from_parts(80, 80))); + let expected_msg = Xcm(vec![QueryResponse { + response: Response::DispatchResult(MaybeErrorCode::Success), + query_id: 42, + max_weight: Weight::from_parts(5000, 5000), + querier: Some(Here.into()), + }]); + let expected_hash = fake_message_hash(&expected_msg); + assert_eq!(sent_xcm(), vec![(Parent.into(), expected_msg, expected_hash)]); +} diff --git a/xcm/xcm-builder/src/tests/version_subscriptions.rs b/xcm/xcm-builder/src/tests/version_subscriptions.rs new file mode 100644 index 000000000000..4aba90c2c82f --- /dev/null +++ b/xcm/xcm-builder/src/tests/version_subscriptions.rs @@ -0,0 +1,149 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use super::*; + +#[test] +fn simple_version_subscriptions_should_work() { + AllowSubsFrom::set(vec![Parent.into()]); + + let origin = Parachain(1000); + let message = Xcm::(vec![ + SetAppendix(Xcm(vec![])), + SubscribeVersion { query_id: 42, max_response_weight: Weight::from_parts(5000, 5000) }, + ]); + let hash = fake_message_hash(&message); + let weight_limit = Weight::from_parts(20, 20); + let r = XcmExecutor::::execute_xcm(origin, message, hash, weight_limit); + assert_eq!(r, Outcome::Error(XcmError::Barrier)); + + let origin = Parachain(1000); + let message = Xcm::(vec![SubscribeVersion { + query_id: 42, + max_response_weight: Weight::from_parts(5000, 5000), + }]); + let hash = fake_message_hash(&message); + let weight_limit = Weight::from_parts(10, 10); + let r = XcmExecutor::::execute_xcm(origin, message.clone(), hash, weight_limit); + assert_eq!(r, Outcome::Error(XcmError::Barrier)); + + let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); + assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + + assert_eq!( + SubscriptionRequests::get(), + vec![(Parent.into(), Some((42, Weight::from_parts(5000, 5000))))] + ); +} + +#[test] +fn version_subscription_instruction_should_work() { + let origin = Parachain(1000); + let message = Xcm::(vec![ + DescendOrigin(X1(AccountIndex64 { index: 1, network: None })), + SubscribeVersion { query_id: 42, max_response_weight: Weight::from_parts(5000, 5000) }, + ]); + let hash = fake_message_hash(&message); + let weight_limit = Weight::from_parts(20, 20); + let r = XcmExecutor::::execute_xcm_in_credit( + origin.clone(), + message, + hash, + weight_limit, + weight_limit, + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(20, 20), XcmError::BadOrigin)); + + let message = Xcm::(vec![ + SetAppendix(Xcm(vec![])), + SubscribeVersion { query_id: 42, max_response_weight: Weight::from_parts(5000, 5000) }, + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm_in_credit( + origin, + message, + hash, + weight_limit, + weight_limit, + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(20, 20))); + + assert_eq!( + SubscriptionRequests::get(), + vec![(Parachain(1000).into(), Some((42, Weight::from_parts(5000, 5000))))] + ); +} + +#[test] +fn simple_version_unsubscriptions_should_work() { + AllowSubsFrom::set(vec![Parent.into()]); + + let origin = Parachain(1000); + let message = Xcm::(vec![SetAppendix(Xcm(vec![])), UnsubscribeVersion]); + let hash = fake_message_hash(&message); + let weight_limit = Weight::from_parts(20, 20); + let r = XcmExecutor::::execute_xcm(origin, message, hash, weight_limit); + assert_eq!(r, Outcome::Error(XcmError::Barrier)); + + let origin = Parachain(1000); + let message = Xcm::(vec![UnsubscribeVersion]); + let hash = fake_message_hash(&message); + let weight_limit = Weight::from_parts(10, 10); + let r = XcmExecutor::::execute_xcm(origin, message.clone(), hash, weight_limit); + assert_eq!(r, Outcome::Error(XcmError::Barrier)); + + let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); + assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + + assert_eq!(SubscriptionRequests::get(), vec![(Parent.into(), None)]); + assert_eq!(sent_xcm(), vec![]); +} + +#[test] +fn version_unsubscription_instruction_should_work() { + let origin = Parachain(1000); + + // Not allowed to do it when origin has been changed. + let message = Xcm::(vec![ + DescendOrigin(X1(AccountIndex64 { index: 1, network: None })), + UnsubscribeVersion, + ]); + let hash = fake_message_hash(&message); + let weight_limit = Weight::from_parts(20, 20); + let r = XcmExecutor::::execute_xcm_in_credit( + origin.clone(), + message, + hash, + weight_limit, + weight_limit, + ); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(20, 20), XcmError::BadOrigin)); + + // Fine to do it when origin is untouched. + let message = Xcm::(vec![SetAppendix(Xcm(vec![])), UnsubscribeVersion]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm_in_credit( + origin, + message, + hash, + weight_limit, + weight_limit, + ); + assert_eq!(r, Outcome::Complete(Weight::from_parts(20, 20))); + + assert_eq!(SubscriptionRequests::get(), vec![(Parachain(1000).into(), None)]); + assert_eq!(sent_xcm(), vec![]); +} diff --git a/xcm/xcm-builder/src/tests/weight.rs b/xcm/xcm-builder/src/tests/weight.rs new file mode 100644 index 000000000000..e98ea8bd0968 --- /dev/null +++ b/xcm/xcm-builder/src/tests/weight.rs @@ -0,0 +1,183 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use super::*; + +#[test] +fn fixed_rate_of_fungible_should_work() { + parameter_types! { + pub static WeightPrice: (AssetId, u128, u128) = + (Here.into(), WEIGHT_REF_TIME_PER_SECOND.into(), WEIGHT_PROOF_SIZE_PER_MB.into()); + } + + let mut trader = FixedRateOfFungible::::new(); + // supplies 100 unit of asset, 80 still remains after purchasing weight + assert_eq!( + trader + .buy_weight(Weight::from_parts(10, 10), fungible_multi_asset(Here.into(), 100).into()), + Ok(fungible_multi_asset(Here.into(), 80).into()), + ); + // should have nothing left, as 5 + 5 = 10, and we supplied 10 units of asset. + assert_eq!( + trader.buy_weight(Weight::from_parts(5, 5), fungible_multi_asset(Here.into(), 10).into()), + Ok(vec![].into()), + ); + // should have 5 left, as there are no proof size components + assert_eq!( + trader.buy_weight(Weight::from_parts(5, 0), fungible_multi_asset(Here.into(), 10).into()), + Ok(fungible_multi_asset(Here.into(), 5).into()), + ); + // not enough to purchase the combined weights + assert_err!( + trader.buy_weight(Weight::from_parts(5, 5), fungible_multi_asset(Here.into(), 5).into()), + XcmError::TooExpensive, + ); +} + +#[test] +fn errors_should_return_unused_weight() { + // we'll let them have message execution for free. + AllowUnpaidFrom::set(vec![Here.into()]); + // We own 1000 of our tokens. + add_asset(Here, (Here, 11u128)); + let mut message = Xcm(vec![ + // First xfer results in an error on the last message only + TransferAsset { + assets: (Here, 1u128).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(), + }, + // Second xfer results in error third message and after + TransferAsset { + assets: (Here, 2u128).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(), + }, + // Third xfer results in error second message and after + TransferAsset { + assets: (Here, 4u128).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(), + }, + ]); + // Weight limit of 70 is needed. + let limit = ::Weigher::weight(&mut message).unwrap(); + assert_eq!(limit, Weight::from_parts(30, 30)); + + let hash = fake_message_hash(&message); + + let r = XcmExecutor::::execute_xcm(Here, message.clone(), hash, limit); + assert_eq!(r, Outcome::Complete(Weight::from_parts(30, 30))); + assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 7u128).into()]); + assert_eq!(asset_list(Here), vec![(Here, 4u128).into()]); + assert_eq!(sent_xcm(), vec![]); + + let r = XcmExecutor::::execute_xcm(Here, message.clone(), hash, limit); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(30, 30), XcmError::NotWithdrawable)); + assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 10u128).into()]); + assert_eq!(asset_list(Here), vec![(Here, 1u128).into()]); + assert_eq!(sent_xcm(), vec![]); + + let r = XcmExecutor::::execute_xcm(Here, message.clone(), hash, limit); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(20, 20), XcmError::NotWithdrawable)); + assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 11u128).into()]); + assert_eq!(asset_list(Here), vec![]); + assert_eq!(sent_xcm(), vec![]); + + let r = XcmExecutor::::execute_xcm(Here, message, hash, limit); + assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NotWithdrawable)); + assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 11u128).into()]); + assert_eq!(asset_list(Here), vec![]); + assert_eq!(sent_xcm(), vec![]); +} + +#[test] +fn weight_bounds_should_respect_instructions_limit() { + MaxInstructions::set(3); + let mut message = Xcm(vec![ClearOrigin; 4]); + // 4 instructions are too many. + assert_eq!(::Weigher::weight(&mut message), Err(())); + + let mut message = + Xcm(vec![SetErrorHandler(Xcm(vec![ClearOrigin])), SetAppendix(Xcm(vec![ClearOrigin]))]); + // 4 instructions are too many, even when hidden within 2. + assert_eq!(::Weigher::weight(&mut message), Err(())); + + let mut message = + Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm( + vec![ClearOrigin], + ))]))]))]); + // 4 instructions are too many, even when it's just one that's 3 levels deep. + assert_eq!(::Weigher::weight(&mut message), Err(())); + + let mut message = + Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm(vec![ClearOrigin]))]))]); + // 3 instructions are OK. + assert_eq!( + ::Weigher::weight(&mut message), + Ok(Weight::from_parts(30, 30)) + ); +} + +#[test] +fn weight_trader_tuple_should_work() { + let para_1: MultiLocation = Parachain(1).into(); + let para_2: MultiLocation = Parachain(2).into(); + + parameter_types! { + pub static HereWeightPrice: (AssetId, u128, u128) = + (Here.into(), WEIGHT_REF_TIME_PER_SECOND.into(), WEIGHT_PROOF_SIZE_PER_MB.into()); + pub static Para1WeightPrice: (AssetId, u128, u128) = + (Parachain(1).into(), WEIGHT_REF_TIME_PER_SECOND.into(), WEIGHT_PROOF_SIZE_PER_MB.into()); + } + + type Traders = ( + // trader one + FixedRateOfFungible, + // trader two + FixedRateOfFungible, + ); + + let mut traders = Traders::new(); + // trader one buys weight + assert_eq!( + traders.buy_weight(Weight::from_parts(5, 5), fungible_multi_asset(Here.into(), 10).into()), + Ok(vec![].into()), + ); + // trader one refunds + assert_eq!( + traders.refund_weight(Weight::from_parts(2, 2)), + Some(fungible_multi_asset(Here.into(), 4)) + ); + + let mut traders = Traders::new(); + // trader one failed; trader two buys weight + assert_eq!( + traders.buy_weight(Weight::from_parts(5, 5), fungible_multi_asset(para_1, 10).into()), + Ok(vec![].into()), + ); + // trader two refunds + assert_eq!( + traders.refund_weight(Weight::from_parts(2, 2)), + Some(fungible_multi_asset(para_1, 4)) + ); + + let mut traders = Traders::new(); + // all traders fails + assert_err!( + traders.buy_weight(Weight::from_parts(5, 5), fungible_multi_asset(para_2, 10).into()), + XcmError::TooExpensive, + ); + // and no refund + assert_eq!(traders.refund_weight(Weight::from_parts(2, 2)), None); +} diff --git a/xcm/xcm-builder/src/universal_exports.rs b/xcm/xcm-builder/src/universal_exports.rs new file mode 100644 index 000000000000..7fe4e2a13cf9 --- /dev/null +++ b/xcm/xcm-builder/src/universal_exports.rs @@ -0,0 +1,382 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! Traits and utilities to help with origin mutation and bridging. + +use frame_support::{ensure, traits::Get}; +use parity_scale_codec::{Decode, Encode}; +use sp_std::{convert::TryInto, marker::PhantomData, prelude::*}; +use xcm::prelude::*; +use xcm_executor::traits::{validate_export, ExportXcm}; +use SendError::*; + +fn ensure_is_remote( + universal_local: impl Into, + dest: impl Into, +) -> Result<(NetworkId, InteriorMultiLocation), MultiLocation> { + let dest = dest.into(); + let universal_local = universal_local.into(); + let local_net = match universal_local.global_consensus() { + Ok(x) => x, + Err(_) => return Err(dest), + }; + let universal_destination: InteriorMultiLocation = universal_local + .into_location() + .appended_with(dest) + .map_err(|x| x.1)? + .try_into()?; + let (remote_dest, remote_net) = match universal_destination.split_first() { + (d, Some(GlobalConsensus(n))) if n != local_net => (d, n), + _ => return Err(dest), + }; + Ok((remote_net, remote_dest)) +} + +/// Implementation of `SendXcm` which uses the given `ExportXcm` implementation in order to forward +/// the message over a bridge. +/// +/// No effort is made to charge for any bridge fees, so this can only be used when it is known +/// that the message sending cannot be abused in any way. +/// +/// This is only useful when the local chain has bridging capabilities. +pub struct UnpaidLocalExporter( + PhantomData<(Exporter, UniversalLocation)>, +); +impl> SendXcm + for UnpaidLocalExporter +{ + type Ticket = Exporter::Ticket; + + fn validate( + dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + let d = dest.take().ok_or(MissingArgument)?; + let universal_source = UniversalLocation::get(); + let devolved = match ensure_is_remote(universal_source, d) { + Ok(x) => x, + Err(d) => { + *dest = Some(d); + return Err(NotApplicable) + }, + }; + let (network, destination) = devolved; + let xcm = xcm.take().ok_or(SendError::MissingArgument)?; + validate_export::(network, 0, universal_source, destination, xcm) + } + + fn deliver(ticket: Exporter::Ticket) -> Result { + Exporter::deliver(ticket) + } +} + +pub trait ExporterFor { + /// Return the locally-routable bridge (if any) capable of forwarding `message` to the + /// `remote_location` on the remote `network`, together with the payment which is required. + /// + /// The payment is specified from the local context, not the bridge chain. This is the + /// total amount to withdraw in to Holding and should cover both payment for the execution on + /// the bridge chain as well as payment for the use of the `ExportMessage` instruction. + fn exporter_for( + network: &NetworkId, + remote_location: &InteriorMultiLocation, + message: &Xcm<()>, + ) -> Option<(MultiLocation, Option)>; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl ExporterFor for Tuple { + fn exporter_for( + network: &NetworkId, + remote_location: &InteriorMultiLocation, + message: &Xcm<()>, + ) -> Option<(MultiLocation, Option)> { + for_tuples!( #( + if let Some(r) = Tuple::exporter_for(network, remote_location, message) { + return Some(r); + } + )* ); + None + } +} + +pub struct NetworkExportTable(sp_std::marker::PhantomData); +impl)>>> ExporterFor + for NetworkExportTable +{ + fn exporter_for( + network: &NetworkId, + _: &InteriorMultiLocation, + _: &Xcm<()>, + ) -> Option<(MultiLocation, Option)> { + T::get().into_iter().find(|(ref j, ..)| j == network).map(|(_, l, p)| (l, p)) + } +} + +/// Implementation of `SendXcm` which wraps the message inside an `ExportMessage` instruction +/// and sends it to a destination known to be able to handle it. +/// +/// No effort is made to make payment to the bridge for its services, so the bridge location +/// must have been configured with a barrier rule allowing unpaid execution for this message +/// coming from our origin. +/// +/// This is only useful if we have special dispensation by the remote bridges to have the +/// `ExportMessage` instruction executed without payment. +pub struct UnpaidRemoteExporter( + PhantomData<(Bridges, Router, UniversalLocation)>, +); +impl> SendXcm + for UnpaidRemoteExporter +{ + type Ticket = Router::Ticket; + + fn validate( + dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + let d = dest.ok_or(MissingArgument)?; + let devolved = ensure_is_remote(UniversalLocation::get(), d).map_err(|_| NotApplicable)?; + let (remote_network, remote_location) = devolved; + let xcm = xcm.take().ok_or(MissingArgument)?; + let (bridge, maybe_payment) = + Bridges::exporter_for(&remote_network, &remote_location, &xcm).ok_or(NotApplicable)?; + ensure!(maybe_payment.is_none(), Unroutable); + + // We then send a normal message to the bridge asking it to export the prepended + // message to the remote chain. This will only work if the bridge will do the message + // export for free. Common-good chains will typically be afforded this. + let message = + Xcm(vec![ExportMessage { network: remote_network, destination: remote_location, xcm }]); + let (v, mut cost) = validate_send::(bridge, message)?; + if let Some(payment) = maybe_payment { + cost.push(payment); + } + Ok((v, cost)) + } + + fn deliver(validation: Router::Ticket) -> Result { + Router::deliver(validation) + } +} + +/// Implementation of `SendXcm` which wraps the message inside an `ExportMessage` instruction +/// and sends it to a destination known to be able to handle it. +/// +/// The `ExportMessage` instruction on the bridge is paid for from the local chain's sovereign +/// account on the bridge. The amount paid is determined through the `ExporterFor` trait. +pub struct SovereignPaidRemoteExporter( + PhantomData<(Bridges, Router, UniversalLocation)>, +); +impl> SendXcm + for SovereignPaidRemoteExporter +{ + type Ticket = Router::Ticket; + + fn validate( + dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + let d = *dest.as_ref().ok_or(MissingArgument)?; + let devolved = ensure_is_remote(UniversalLocation::get(), d).map_err(|_| NotApplicable)?; + let (remote_network, remote_location) = devolved; + + let xcm = xcm.take().ok_or(MissingArgument)?; + let (bridge, maybe_payment) = + Bridges::exporter_for(&remote_network, &remote_location, &xcm).ok_or(NotApplicable)?; + + let local_from_bridge = + UniversalLocation::get().invert_target(&bridge).map_err(|_| Unroutable)?; + let export_instruction = + ExportMessage { network: remote_network, destination: remote_location, xcm }; + + let message = Xcm(if let Some(ref payment) = maybe_payment { + let fees = payment + .clone() + .reanchored(&bridge, UniversalLocation::get()) + .map_err(|_| Unroutable)?; + vec![ + WithdrawAsset(fees.clone().into()), + BuyExecution { fees, weight_limit: Unlimited }, + export_instruction, + RefundSurplus, + DepositAsset { assets: All.into(), beneficiary: local_from_bridge }, + ] + } else { + vec![export_instruction] + }); + + // We then send a normal message to the bridge asking it to export the prepended + // message to the remote chain. + let (v, mut cost) = validate_send::(bridge, message)?; + if let Some(bridge_payment) = maybe_payment { + cost.push(bridge_payment); + } + Ok((v, cost)) + } + + fn deliver(ticket: Router::Ticket) -> Result { + Router::deliver(ticket) + } +} + +pub trait DispatchBlob { + /// Dispatches an incoming blob and returns the unexpectable weight consumed by the dispatch. + fn dispatch_blob(blob: Vec) -> Result<(), DispatchBlobError>; +} + +pub trait HaulBlob { + /// Sends a blob over some point-to-point link. This will generally be implemented by a bridge. + fn haul_blob(blob: Vec) -> Result<(), HaulBlobError>; +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum HaulBlobError { + /// Represents point-to-point link failure with a human-readable explanation of the specific issue is provided. + Transport(&'static str), +} + +impl From for SendError { + fn from(err: HaulBlobError) -> Self { + match err { + HaulBlobError::Transport(reason) => SendError::Transport(reason), + } + } +} + +#[derive(Clone, Encode, Decode)] +pub struct BridgeMessage { + /// The message destination as a *Universal Location*. This means it begins with a + /// `GlobalConsensus` junction describing the network under which global consensus happens. + /// If this does not match our global consensus then it's a fatal error. + universal_dest: VersionedInteriorMultiLocation, + message: VersionedXcm<()>, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DispatchBlobError { + Unbridgable, + InvalidEncoding, + UnsupportedLocationVersion, + UnsupportedXcmVersion, + RoutingError, + NonUniversalDestination, + WrongGlobal, +} + +pub struct BridgeBlobDispatcher(PhantomData<(Router, OurPlace)>); +impl> DispatchBlob + for BridgeBlobDispatcher +{ + fn dispatch_blob(blob: Vec) -> Result<(), DispatchBlobError> { + let our_universal = OurPlace::get(); + let our_global = + our_universal.global_consensus().map_err(|()| DispatchBlobError::Unbridgable)?; + let BridgeMessage { universal_dest, message } = + Decode::decode(&mut &blob[..]).map_err(|_| DispatchBlobError::InvalidEncoding)?; + let universal_dest: InteriorMultiLocation = universal_dest + .try_into() + .map_err(|_| DispatchBlobError::UnsupportedLocationVersion)?; + // `universal_dest` is the desired destination within the universe: first we need to check + // we're in the right global consensus. + let intended_global = universal_dest + .global_consensus() + .map_err(|()| DispatchBlobError::NonUniversalDestination)?; + ensure!(intended_global == our_global, DispatchBlobError::WrongGlobal); + let dest = universal_dest.relative_to(&our_universal); + let message: Xcm<()> = + message.try_into().map_err(|_| DispatchBlobError::UnsupportedXcmVersion)?; + let _ = send_xcm::(dest, message).map_err(|_| DispatchBlobError::RoutingError)?; + Ok(()) + } +} + +pub struct HaulBlobExporter( + PhantomData<(Bridge, BridgedNetwork, Price)>, +); +impl, Price: Get> ExportXcm + for HaulBlobExporter +{ + type Ticket = (Vec, XcmHash); + + fn validate( + network: NetworkId, + _channel: u32, + universal_source: &mut Option, + destination: &mut Option, + message: &mut Option>, + ) -> Result<((Vec, XcmHash), MultiAssets), SendError> { + let bridged_network = BridgedNetwork::get(); + ensure!(&network == &bridged_network, SendError::NotApplicable); + // We don't/can't use the `channel` for this adapter. + let dest = destination.take().ok_or(SendError::MissingArgument)?; + let universal_dest = match dest.pushed_front_with(GlobalConsensus(bridged_network)) { + Ok(d) => d.into(), + Err((dest, _)) => { + *destination = Some(dest); + return Err(SendError::NotApplicable) + }, + }; + let (local_net, local_sub) = universal_source + .take() + .ok_or(SendError::MissingArgument)? + .split_global() + .map_err(|()| SendError::Unroutable)?; + let mut inner: Xcm<()> = vec![UniversalOrigin(GlobalConsensus(local_net))].into(); + if local_sub != Here { + inner.inner_mut().push(DescendOrigin(local_sub)); + } + inner + .inner_mut() + .extend(message.take().ok_or(SendError::MissingArgument)?.into_iter()); + let message = VersionedXcm::from(inner); + let hash = message.using_encoded(sp_io::hashing::blake2_256); + let blob = BridgeMessage { universal_dest, message }.encode(); + Ok(((blob, hash), Price::get())) + } + + fn deliver((blob, hash): (Vec, XcmHash)) -> Result { + Bridge::haul_blob(blob)?; + Ok(hash) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ensure_is_remote_works() { + // A Kusama parachain is remote from the Polkadot Relay. + let x = ensure_is_remote(Polkadot, (Parent, Kusama, Parachain(1000))); + assert_eq!(x, Ok((Kusama, Parachain(1000).into()))); + + // Polkadot Relay is remote from a Kusama parachain. + let x = ensure_is_remote((Kusama, Parachain(1000)), (Parent, Parent, Polkadot)); + assert_eq!(x, Ok((Polkadot, Here))); + + // Our own parachain is local. + let x = ensure_is_remote(Polkadot, Parachain(1000)); + assert_eq!(x, Err(Parachain(1000).into())); + + // Polkadot's parachain is not remote if we are Polkadot. + let x = ensure_is_remote(Polkadot, (Parent, Polkadot, Parachain(1000))); + assert_eq!(x, Err((Parent, Polkadot, Parachain(1000)).into())); + + // If we don't have a consensus ancestor, then we cannot determine remoteness. + let x = ensure_is_remote((), (Parent, Polkadot, Parachain(1000))); + assert_eq!(x, Err((Parent, Polkadot, Parachain(1000)).into())); + } +} diff --git a/xcm/xcm-builder/src/weight.rs b/xcm/xcm-builder/src/weight.rs index 2ab8afde2da5..815ad3ae9784 100644 --- a/xcm/xcm-builder/src/weight.rs +++ b/xcm/xcm-builder/src/weight.rs @@ -17,7 +17,10 @@ use frame_support::{ dispatch::GetDispatchInfo, traits::{tokens::currency::Currency as CurrencyT, Get, OnUnbalanced as OnUnbalancedT}, - weights::{constants::WEIGHT_REF_TIME_PER_SECOND, WeightToFee as WeightToFeeT}, + weights::{ + constants::{WEIGHT_PROOF_SIZE_PER_MB, WEIGHT_REF_TIME_PER_SECOND}, + WeightToFee as WeightToFeeT, + }, }; use parity_scale_codec::Decode; use sp_runtime::traits::{SaturatedConversion, Saturating, Zero}; @@ -44,10 +47,10 @@ impl, C: Decode + GetDispatchInfo, M: Get> WeightBounds impl, C: Decode + GetDispatchInfo, M> FixedWeightBounds { fn weight_with_limit(message: &Xcm, instrs_limit: &mut u32) -> Result { - let mut r: Weight = 0; + let mut r: Weight = Weight::zero(); *instrs_limit = instrs_limit.checked_sub(message.0.len() as u32).ok_or(())?; for m in message.0.iter() { - r = r.checked_add(Self::instr_weight_with_limit(m, instrs_limit)?).ok_or(())?; + r = r.checked_add(&Self::instr_weight_with_limit(m, instrs_limit)?).ok_or(())?; } Ok(r) } @@ -55,14 +58,12 @@ impl, C: Decode + GetDispatchInfo, M> FixedWeightBounds instruction: &Instruction, instrs_limit: &mut u32, ) -> Result { - T::get() - .checked_add(match instruction { - Transact { require_weight_at_most, .. } => *require_weight_at_most, - SetErrorHandler(xcm) | SetAppendix(xcm) => - Self::weight_with_limit(xcm, instrs_limit)?, - _ => 0, - }) - .ok_or(()) + let instr_weight = match instruction { + Transact { require_weight_at_most, .. } => *require_weight_at_most, + SetErrorHandler(xcm) | SetAppendix(xcm) => Self::weight_with_limit(xcm, instrs_limit)?, + _ => Weight::zero(), + }; + T::get().checked_add(&instr_weight).ok_or(()) } } @@ -92,10 +93,10 @@ where Instruction: xcm::GetWeight, { fn weight_with_limit(message: &Xcm, instrs_limit: &mut u32) -> Result { - let mut r: Weight = 0; + let mut r: Weight = Weight::zero(); *instrs_limit = instrs_limit.checked_sub(message.0.len() as u32).ok_or(())?; for m in message.0.iter() { - r = r.checked_add(Self::instr_weight_with_limit(m, instrs_limit)?).ok_or(())?; + r = r.checked_add(&Self::instr_weight_with_limit(m, instrs_limit)?).ok_or(())?; } Ok(r) } @@ -104,15 +105,12 @@ where instrs_limit: &mut u32, ) -> Result { use xcm::GetWeight; - instruction - .weight() - .checked_add(match instruction { - Transact { require_weight_at_most, .. } => *require_weight_at_most, - SetErrorHandler(xcm) | SetAppendix(xcm) => - Self::weight_with_limit(xcm, instrs_limit)?, - _ => 0, - }) - .ok_or(()) + let instr_weight = match instruction { + Transact { require_weight_at_most, .. } => *require_weight_at_most, + SetErrorHandler(xcm) | SetAppendix(xcm) => Self::weight_with_limit(xcm, instrs_limit)?, + _ => Weight::zero(), + }; + instruction.weight().checked_add(&instr_weight).ok_or(()) } } @@ -128,74 +126,18 @@ impl TakeRevenue for () { fn take_revenue(_revenue: MultiAsset) {} } -/// Simple fee calculator that requires payment in a single concrete fungible at a fixed rate. -/// -/// The constant `Get` type parameter should be the concrete fungible ID and the amount of it required for -/// one second of weight. -#[deprecated = "Use `FixedRateOfFungible` instead"] -pub struct FixedRateOfConcreteFungible, R: TakeRevenue>( - Weight, - u128, - PhantomData<(T, R)>, -); -#[allow(deprecated)] -impl, R: TakeRevenue> WeightTrader - for FixedRateOfConcreteFungible -{ - fn new() -> Self { - Self(0, 0, PhantomData) - } - - fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { - log::trace!( - target: "xcm::weight", - "FixedRateOfConcreteFungible::buy_weight weight: {:?}, payment: {:?}", - weight, payment, - ); - let (id, units_per_second) = T::get(); - let amount = units_per_second * (weight as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128); - let unused = - payment.checked_sub((id, amount).into()).map_err(|_| XcmError::TooExpensive)?; - self.0 = self.0.saturating_add(weight); - self.1 = self.1.saturating_add(amount); - Ok(unused) - } - - fn refund_weight(&mut self, weight: Weight) -> Option { - log::trace!(target: "xcm::weight", "FixedRateOfConcreteFungible::refund_weight weight: {:?}", weight); - let (id, units_per_second) = T::get(); - let weight = weight.min(self.0); - let amount = units_per_second * (weight as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128); - self.0 -= weight; - self.1 = self.1.saturating_sub(amount); - if amount > 0 { - Some((Concrete(id), amount).into()) - } else { - None - } - } -} -#[allow(deprecated)] -impl, R: TakeRevenue> Drop for FixedRateOfConcreteFungible { - fn drop(&mut self) { - if self.1 > 0 { - R::take_revenue((Concrete(T::get().0), self.1).into()); - } - } -} - /// Simple fee calculator that requires payment in a single fungible at a fixed rate. /// -/// The constant `Get` type parameter should be the fungible ID and the amount of it required for -/// one second of weight. -pub struct FixedRateOfFungible, R: TakeRevenue>( +/// The constant `Get` type parameter should be the fungible ID, the amount of it required for one +/// second of weight and the amount required for 1 MB of proof. +pub struct FixedRateOfFungible, R: TakeRevenue>( Weight, u128, PhantomData<(T, R)>, ); -impl, R: TakeRevenue> WeightTrader for FixedRateOfFungible { +impl, R: TakeRevenue> WeightTrader for FixedRateOfFungible { fn new() -> Self { - Self(0, 0, PhantomData) + Self(Weight::zero(), 0, PhantomData) } fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { @@ -204,8 +146,10 @@ impl, R: TakeRevenue> WeightTrader for FixedRateOfFungib "FixedRateOfFungible::buy_weight weight: {:?}, payment: {:?}", weight, payment, ); - let (id, units_per_second) = T::get(); - let amount = units_per_second * (weight as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128); + let (id, units_per_second, units_per_mb) = T::get(); + let amount = (units_per_second * (weight.ref_time() as u128) / + (WEIGHT_REF_TIME_PER_SECOND as u128)) + + (units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128)); if amount == 0 { return Ok(payment) } @@ -218,9 +162,11 @@ impl, R: TakeRevenue> WeightTrader for FixedRateOfFungib fn refund_weight(&mut self, weight: Weight) -> Option { log::trace!(target: "xcm::weight", "FixedRateOfFungible::refund_weight weight: {:?}", weight); - let (id, units_per_second) = T::get(); + let (id, units_per_second, units_per_mb) = T::get(); let weight = weight.min(self.0); - let amount = units_per_second * (weight as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128); + let amount = (units_per_second * (weight.ref_time() as u128) / + (WEIGHT_REF_TIME_PER_SECOND as u128)) + + (units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128)); self.0 -= weight; self.1 = self.1.saturating_sub(amount); if amount > 0 { @@ -231,7 +177,7 @@ impl, R: TakeRevenue> WeightTrader for FixedRateOfFungib } } -impl, R: TakeRevenue> Drop for FixedRateOfFungible { +impl, R: TakeRevenue> Drop for FixedRateOfFungible { fn drop(&mut self) { if self.1 > 0 { R::take_revenue((T::get().0, self.1).into()); @@ -261,13 +207,12 @@ impl< > WeightTrader for UsingComponents { fn new() -> Self { - Self(0, Zero::zero(), PhantomData) + Self(Weight::zero(), Zero::zero(), PhantomData) } fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { log::trace!(target: "xcm::weight", "UsingComponents::buy_weight weight: {:?}, payment: {:?}", weight, payment); - let amount = - WeightToFee::weight_to_fee(&frame_support::weights::Weight::from_ref_time(weight)); + let amount = WeightToFee::weight_to_fee(&weight); let u128_amount: u128 = amount.try_into().map_err(|_| XcmError::Overflow)?; let required = (Concrete(AssetId::get()), u128_amount).into(); let unused = payment.checked_sub(required).map_err(|_| XcmError::TooExpensive)?; @@ -279,8 +224,7 @@ impl< fn refund_weight(&mut self, weight: Weight) -> Option { log::trace!(target: "xcm::weight", "UsingComponents::refund_weight weight: {:?}", weight); let weight = weight.min(self.0); - let amount = - WeightToFee::weight_to_fee(&frame_support::weights::Weight::from_ref_time(weight)); + let amount = WeightToFee::weight_to_fee(&weight); self.0 -= weight; self.1 = self.1.saturating_sub(amount); let amount: u128 = amount.saturated_into(); diff --git a/xcm/xcm-builder/tests/mock/mod.rs b/xcm/xcm-builder/tests/mock/mod.rs index 30e12a12e073..79cecfbdcb9a 100644 --- a/xcm/xcm-builder/tests/mock/mod.rs +++ b/xcm/xcm-builder/tests/mock/mod.rs @@ -17,7 +17,9 @@ use frame_support::{ construct_runtime, parameter_types, traits::{Everything, Nothing}, + weights::Weight, }; +use parity_scale_codec::Encode; use sp_core::H256; use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; use sp_std::cell::RefCell; @@ -31,7 +33,7 @@ use xcm_builder::{ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, - IsChildSystemParachain, IsConcrete, LocationInverter, SignedAccountId32AsNative, + IsChildSystemParachain, IsConcrete, MintLocation, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, }; @@ -39,16 +41,27 @@ pub type AccountId = AccountId32; pub type Balance = u128; thread_local! { - pub static SENT_XCM: RefCell> = RefCell::new(Vec::new()); + pub static SENT_XCM: RefCell> = RefCell::new(Vec::new()); } -pub fn sent_xcm() -> Vec<(MultiLocation, opaque::Xcm)> { +pub fn sent_xcm() -> Vec<(MultiLocation, opaque::Xcm, XcmHash)> { SENT_XCM.with(|q| (*q.borrow()).clone()) } pub struct TestSendXcm; impl SendXcm for TestSendXcm { - fn send_xcm(dest: impl Into, msg: opaque::Xcm) -> SendResult { - SENT_XCM.with(|q| q.borrow_mut().push((dest.into(), msg))); - Ok(()) + type Ticket = (MultiLocation, Xcm<()>, XcmHash); + fn validate( + dest: &mut Option, + msg: &mut Option>, + ) -> SendResult<(MultiLocation, Xcm<()>, XcmHash)> { + let msg = msg.take().unwrap(); + let hash = fake_message_hash(&msg); + let triplet = (dest.take().unwrap(), msg, hash); + Ok((triplet, MultiAssets::new())) + } + fn deliver(triplet: (MultiLocation, Xcm<()>, XcmHash)) -> Result { + let hash = triplet.2; + SENT_XCM.with(|q| q.borrow_mut().push(triplet)); + Ok(hash) } } @@ -115,8 +128,8 @@ impl configuration::Config for Runtime { parameter_types! { pub const KsmLocation: MultiLocation = MultiLocation::here(); pub const KusamaNetwork: NetworkId = NetworkId::Kusama; - pub Ancestry: MultiLocation = Here.into(); - pub CheckAccount: AccountId = XcmPallet::check_account(); + pub UniversalLocation: InteriorMultiLocation = Here; + pub CheckAccount: (AccountId, MintLocation) = (XcmPallet::check_account(), MintLocation::Local); } pub type SovereignAccountOf = @@ -140,8 +153,8 @@ type LocalOriginConverter = ( ); parameter_types! { - pub const BaseXcmWeight: u64 = 1_000_000_000; - pub KsmPerSecond: (AssetId, u128) = (KsmLocation::get().into(), 1); + pub const BaseXcmWeight: Weight = Weight::from_parts(1_000_000_000, 1024); + pub KsmPerSecondPerByte: (AssetId, u128, u128) = (KsmLocation::get().into(), 1, 1); } pub type Barrier = ( @@ -152,10 +165,12 @@ pub type Barrier = ( ); parameter_types! { - pub const KusamaForStatemine: (MultiAssetFilter, MultiLocation) = - (MultiAssetFilter::Wild(WildMultiAsset::AllOf { id: Concrete(MultiLocation::here()), fun: WildFungible }), X1(Parachain(1000)).into()); + pub KusamaForStatemine: (MultiAssetFilter, MultiLocation) = + (Wild(AllOf { id: Concrete(Here.into()), fun: WildFungible }), Parachain(1000).into()); pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 4; } + pub type TrustedTeleporters = (xcm_builder::Case,); pub struct XcmConfig; @@ -166,21 +181,35 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = LocalOriginConverter; type IsReserve = (); type IsTeleporter = TrustedTeleporters; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; - type Trader = FixedRateOfFungible; + type Trader = FixedRateOfFungible; type ResponseHandler = XcmPallet; type AssetTrap = XcmPallet; + type AssetLocker = (); + type AssetExchanger = (); type AssetClaims = XcmPallet; type SubscriptionService = XcmPallet; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; } pub type LocalOriginToLocation = SignedToAccountId32; +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Here.into()); +} + impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; type XcmRouter = TestSendXcm; // Anyone can execute XCM messages locally... @@ -194,6 +223,14 @@ impl pallet_xcm::Config for Runtime { type RuntimeOrigin = RuntimeOrigin; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type TrustedLockers = (); + type SovereignAccountOf = (); + type Currency = Balances; + type CurrencyMatcher = IsConcrete; + type MaxLockers = frame_support::traits::ConstU32<8>; + type WeightInfo = pallet_xcm::TestWeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; } impl origin::Config for Runtime {} @@ -225,3 +262,7 @@ pub fn kusama_like_with_balances(balances: Vec<(AccountId, Balance)>) -> sp_io:: ext.execute_with(|| System::set_block_number(1)); ext } + +pub fn fake_message_hash(message: &Xcm) -> XcmHash { + message.using_encoded(sp_io::hashing::blake2_256) +} diff --git a/xcm/xcm-builder/tests/scenarios.rs b/xcm/xcm-builder/tests/scenarios.rs index d826e652c5d6..05227fbd742c 100644 --- a/xcm/xcm-builder/tests/scenarios.rs +++ b/xcm/xcm-builder/tests/scenarios.rs @@ -16,9 +16,10 @@ mod mock; +use frame_support::weights::Weight; use mock::{ - kusama_like_with_balances, AccountId, Balance, Balances, BaseXcmWeight, System, XcmConfig, - CENTS, + fake_message_hash, kusama_like_with_balances, AccountId, Balance, Balances, BaseXcmWeight, + System, XcmConfig, CENTS, }; use polkadot_parachain::primitives::Id as ParaId; use sp_runtime::traits::AccountIdConversion; @@ -36,7 +37,7 @@ fn buy_execution() -> Instruction { } /// Scenario: -/// A parachain transfers funds on the relaychain to another parachain's account. +/// A parachain transfers funds on the relay-chain to another parachain's account. /// /// Asserts that the parachain accounts are updated as expected. #[test] @@ -46,20 +47,17 @@ fn withdraw_and_deposit_works() { kusama_like_with_balances(balances).execute_with(|| { let other_para_id = 3000; let amount = REGISTER_AMOUNT; - let weight = 3 * BaseXcmWeight::get(); - let r = XcmExecutor::::execute_xcm( - Parachain(PARA_ID).into(), - Xcm(vec![ - WithdrawAsset((Here, amount).into()), - buy_execution(), - DepositAsset { - assets: All.into(), - max_assets: 1, - beneficiary: Parachain(other_para_id).into(), - }, - ]), - weight, - ); + let weight = BaseXcmWeight::get() * 3; + let message = Xcm(vec![ + WithdrawAsset((Here, amount).into()), + buy_execution(), + DepositAsset { + assets: AllCounted(1).into(), + beneficiary: Parachain(other_para_id).into(), + }, + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm(Parachain(PARA_ID), message, hash, weight); assert_eq!(r, Outcome::Complete(weight)); let other_para_acc: AccountId = ParaId::from(other_para_id).into_account_truncating(); assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE - amount); @@ -78,13 +76,16 @@ fn transfer_asset_works() { kusama_like_with_balances(balances).execute_with(|| { let amount = REGISTER_AMOUNT; let weight = BaseXcmWeight::get(); + let message = Xcm(vec![TransferAsset { + assets: (Here, amount).into(), + beneficiary: AccountId32 { network: None, id: bob.clone().into() }.into(), + }]); + let hash = fake_message_hash(&message); // Use `execute_xcm_in_credit` here to pass through the barrier let r = XcmExecutor::::execute_xcm_in_credit( - AccountId32 { network: NetworkId::Any, id: ALICE.into() }, - Xcm(vec![TransferAsset { - assets: (Here, amount).into(), - beneficiary: AccountId32 { network: NetworkId::Any, id: bob.clone().into() }.into(), - }]), + AccountId32 { network: None, id: ALICE.into() }, + message, + hash, weight, weight, ); @@ -106,36 +107,31 @@ fn transfer_asset_works() { /// /// Asserts that the balances are updated correctly and the expected XCM is sent. #[test] -fn query_holding_works() { +fn report_holding_works() { use xcm::opaque::latest::prelude::*; let para_acc: AccountId = ParaId::from(PARA_ID).into_account_truncating(); let balances = vec![(ALICE, INITIAL_BALANCE), (para_acc.clone(), INITIAL_BALANCE)]; kusama_like_with_balances(balances).execute_with(|| { let other_para_id = 3000; let amount = REGISTER_AMOUNT; - let query_id = 1234; - let weight = 4 * BaseXcmWeight::get(); - let max_response_weight = 1_000_000_000; - let r = XcmExecutor::::execute_xcm( - Parachain(PARA_ID).into(), - Xcm(vec![ - WithdrawAsset((Here, amount).into()), - buy_execution(), - DepositAsset { - assets: All.into(), - max_assets: 1, - beneficiary: OnlyChild.into(), // invalid destination - }, - // is not triggered becasue the deposit fails - QueryHolding { - query_id, - dest: Parachain(PARA_ID).into(), - assets: All.into(), - max_response_weight, - }, - ]), - weight, - ); + let weight = BaseXcmWeight::get() * 4; + let response_info = QueryResponseInfo { + destination: Parachain(PARA_ID).into(), + query_id: 1234, + max_weight: Weight::from_parts(1_000_000_000, 1_000_000_000), + }; + let message = Xcm(vec![ + WithdrawAsset((Here, amount).into()), + buy_execution(), + DepositAsset { + assets: AllCounted(1).into(), + beneficiary: OnlyChild.into(), // invalid destination + }, + // is not triggered becasue the deposit fails + ReportHolding { response_info: response_info.clone(), assets: All.into() }, + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm(Parachain(PARA_ID), message, hash, weight); assert_eq!( r, Outcome::Incomplete( @@ -148,40 +144,32 @@ fn query_holding_works() { assert_eq!(Balances::free_balance(para_acc.clone()), INITIAL_BALANCE - amount); // now do a successful transfer - let r = XcmExecutor::::execute_xcm( - Parachain(PARA_ID).into(), - Xcm(vec![ - WithdrawAsset((Here, amount).into()), - buy_execution(), - DepositAsset { - assets: All.into(), - max_assets: 1, - beneficiary: Parachain(other_para_id).into(), - }, - // used to get a notification in case of success - QueryHolding { - query_id, - dest: Parachain(PARA_ID).into(), - assets: All.into(), - max_response_weight: 1_000_000_000, - }, - ]), - weight, - ); + let message = Xcm(vec![ + WithdrawAsset((Here, amount).into()), + buy_execution(), + DepositAsset { + assets: AllCounted(1).into(), + beneficiary: Parachain(other_para_id).into(), + }, + // used to get a notification in case of success + ReportHolding { response_info: response_info.clone(), assets: AllCounted(1).into() }, + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm(Parachain(PARA_ID), message, hash, weight); assert_eq!(r, Outcome::Complete(weight)); let other_para_acc: AccountId = ParaId::from(other_para_id).into_account_truncating(); assert_eq!(Balances::free_balance(other_para_acc), amount); assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE - 2 * amount); + let expected_msg = Xcm(vec![QueryResponse { + query_id: response_info.query_id, + response: Response::Assets(vec![].into()), + max_weight: response_info.max_weight, + querier: Some(Here.into()), + }]); + let expected_hash = fake_message_hash(&expected_msg); assert_eq!( mock::sent_xcm(), - vec![( - Parachain(PARA_ID).into(), - Xcm(vec![QueryResponse { - query_id, - response: Response::Assets(vec![].into()), - max_weight: 1_000_000_000, - }]), - )] + vec![(Parachain(PARA_ID).into(), expected_msg, expected_hash,)] ); }); } @@ -206,73 +194,60 @@ fn teleport_to_statemine_works() { let teleport_effects = vec![ buy_execution(), // unchecked mock value DepositAsset { - assets: All.into(), - max_assets: 1, - beneficiary: (1, Parachain(PARA_ID)).into(), + assets: AllCounted(1).into(), + beneficiary: (Parent, Parachain(PARA_ID)).into(), }, ]; - let weight = 3 * BaseXcmWeight::get(); + let weight = BaseXcmWeight::get() * 3; // teleports are allowed to community chains, even in the absence of trust from their side. - let r = XcmExecutor::::execute_xcm( - Parachain(PARA_ID).into(), - Xcm(vec![ - WithdrawAsset((Here, amount).into()), - buy_execution(), - InitiateTeleport { - assets: All.into(), - dest: Parachain(other_para_id).into(), - xcm: Xcm(teleport_effects.clone()), - }, - ]), - weight, - ); + let message = Xcm(vec![ + WithdrawAsset((Here, amount).into()), + buy_execution(), + InitiateTeleport { + assets: All.into(), + dest: Parachain(other_para_id).into(), + xcm: Xcm(teleport_effects.clone()), + }, + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm(Parachain(PARA_ID), message, hash, weight); assert_eq!(r, Outcome::Complete(weight)); + let expected_msg = Xcm(vec![ReceiveTeleportedAsset((Parent, amount).into()), ClearOrigin] + .into_iter() + .chain(teleport_effects.clone().into_iter()) + .collect()); + let expected_hash = fake_message_hash(&expected_msg); assert_eq!( mock::sent_xcm(), - vec![( - Parachain(other_para_id).into(), - Xcm(vec![ReceiveTeleportedAsset((Parent, amount).into()), ClearOrigin,] - .into_iter() - .chain(teleport_effects.clone().into_iter()) - .collect()) - )] + vec![(Parachain(other_para_id).into(), expected_msg, expected_hash,)] ); // teleports are allowed from statemine to kusama. - let r = XcmExecutor::::execute_xcm( - Parachain(PARA_ID).into(), - Xcm(vec![ - WithdrawAsset((Here, amount).into()), - buy_execution(), - InitiateTeleport { - assets: All.into(), - dest: Parachain(statemine_id).into(), - xcm: Xcm(teleport_effects.clone()), - }, - ]), - weight, - ); + let message = Xcm(vec![ + WithdrawAsset((Here, amount).into()), + buy_execution(), + InitiateTeleport { + assets: All.into(), + dest: Parachain(statemine_id).into(), + xcm: Xcm(teleport_effects.clone()), + }, + ]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm(Parachain(PARA_ID), message, hash, weight); assert_eq!(r, Outcome::Complete(weight)); // 2 * amount because of the other teleport above assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE - 2 * amount); + let expected_msg = Xcm(vec![ReceiveTeleportedAsset((Parent, amount).into()), ClearOrigin] + .into_iter() + .chain(teleport_effects.clone().into_iter()) + .collect()); + let expected_hash = fake_message_hash(&expected_msg); assert_eq!( mock::sent_xcm(), vec![ - ( - Parachain(other_para_id).into(), - Xcm(vec![ReceiveTeleportedAsset((Parent, amount).into()), ClearOrigin,] - .into_iter() - .chain(teleport_effects.clone().into_iter()) - .collect()), - ), - ( - Parachain(statemine_id).into(), - Xcm(vec![ReceiveTeleportedAsset((Parent, amount).into()), ClearOrigin,] - .into_iter() - .chain(teleport_effects.clone().into_iter()) - .collect()), - ) + (Parachain(other_para_id).into(), expected_msg.clone(), expected_hash,), + (Parachain(statemine_id).into(), expected_msg, expected_hash,) ] ); }); @@ -295,37 +270,32 @@ fn reserve_based_transfer_works() { let transfer_effects = vec![ buy_execution(), // unchecked mock value DepositAsset { - assets: All.into(), - max_assets: 1, - beneficiary: (1, Parachain(PARA_ID)).into(), + assets: AllCounted(1).into(), + beneficiary: (Parent, Parachain(PARA_ID)).into(), }, ]; - let weight = 3 * BaseXcmWeight::get(); - let r = XcmExecutor::::execute_xcm( - Parachain(PARA_ID).into(), - Xcm(vec![ - WithdrawAsset((Here, amount).into()), - buy_execution(), - DepositReserveAsset { - assets: All.into(), - max_assets: 1, - dest: Parachain(other_para_id).into(), - xcm: Xcm(transfer_effects.clone()), - }, - ]), - weight, - ); + let message = Xcm(vec![ + WithdrawAsset((Here, amount).into()), + buy_execution(), + DepositReserveAsset { + assets: AllCounted(1).into(), + dest: Parachain(other_para_id).into(), + xcm: Xcm(transfer_effects.clone()), + }, + ]); + let hash = fake_message_hash(&message); + let weight = BaseXcmWeight::get() * 3; + let r = XcmExecutor::::execute_xcm(Parachain(PARA_ID), message, hash, weight); assert_eq!(r, Outcome::Complete(weight)); assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE - amount); + let expected_msg = Xcm(vec![ReserveAssetDeposited((Parent, amount).into()), ClearOrigin] + .into_iter() + .chain(transfer_effects.into_iter()) + .collect()); + let expected_hash = fake_message_hash(&expected_msg); assert_eq!( mock::sent_xcm(), - vec![( - Parachain(other_para_id).into(), - Xcm(vec![ReserveAssetDeposited((Parent, amount).into()), ClearOrigin,] - .into_iter() - .chain(transfer_effects.into_iter()) - .collect()) - )] + vec![(Parachain(other_para_id).into(), expected_msg, expected_hash,)] ); }); } diff --git a/xcm/xcm-executor/Cargo.toml b/xcm/xcm-executor/Cargo.toml index a87bb02283f3..ecbff92a46ec 100644 --- a/xcm/xcm-executor/Cargo.toml +++ b/xcm/xcm-executor/Cargo.toml @@ -7,6 +7,7 @@ version.workspace = true [dependencies] impl-trait-for-tuples = "0.2.2" +environmental = { version = "1.1.4", default-features = false } parity-scale-codec = { version = "3.1.5", default-features = false, features = ["derive"] } xcm = { path = "..", default-features = false } sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } @@ -14,6 +15,7 @@ sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", de sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-weights = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } log = { version = "0.4.17", default-features = false } frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false, optional = true } @@ -31,6 +33,7 @@ std = [ "sp-arithmetic/std", "sp-core/std", "sp-runtime/std", + "sp-weights/std", "frame-support/std", "log/std", ] diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index f038e45d3edc..a3212c798dab 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -17,15 +17,17 @@ #![cfg_attr(not(feature = "std"), no_std)] #![cfg(test)] +use frame_support::{codec::Encode, dispatch::GetDispatchInfo, weights::Weight}; use polkadot_test_client::{ BlockBuilderExt, ClientBlockImportExt, DefaultTestClientBuilderExt, ExecutionStrategy, InitPolkadotBlockBuilder, TestClientBuilder, TestClientBuilderExt, }; -use polkadot_test_runtime::pallet_test_notifier; +use polkadot_test_runtime::{pallet_test_notifier, xcm_config::XcmConfig}; use polkadot_test_service::construct_extrinsic; use sp_runtime::traits::Block; use sp_state_machine::InspectState; use xcm::{latest::prelude::*, VersionedResponse, VersionedXcm}; +use xcm_executor::traits::WeightBounds; #[test] fn basic_buy_fees_message_executes() { @@ -37,7 +39,7 @@ fn basic_buy_fees_message_executes() { let msg = Xcm(vec![ WithdrawAsset((Parent, 100).into()), BuyExecution { fees: (Parent, 1).into(), weight_limit: Unlimited }, - DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Parent.into() }, + DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Parent.into() }, ]); let mut block_builder = client.init_polkadot_block_builder(); @@ -46,7 +48,7 @@ fn basic_buy_fees_message_executes() { &client, polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute { message: Box::new(VersionedXcm::from(msg)), - max_weight: 1_000_000_000, + max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), }), sp_keyring::Sr25519Keyring::Alice, 0, @@ -70,6 +72,59 @@ fn basic_buy_fees_message_executes() { }); } +#[test] +fn transact_recursion_limit_works() { + sp_tracing::try_init_simple(); + let mut client = TestClientBuilder::new() + .set_execution_strategy(ExecutionStrategy::AlwaysWasm) + .build(); + + let mut msg = Xcm(vec![ClearOrigin]); + let max_weight = ::Weigher::weight(&mut msg).unwrap(); + let mut call = polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute { + message: Box::new(VersionedXcm::from(msg)), + max_weight, + }); + + for _ in 0..11 { + let mut msg = Xcm(vec![ + WithdrawAsset((Parent, 1_000).into()), + BuyExecution { fees: (Parent, 1).into(), weight_limit: Unlimited }, + Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: call.get_dispatch_info().weight, + call: call.encode().into(), + }, + ]); + let max_weight = ::Weigher::weight(&mut msg).unwrap(); + call = polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute { + message: Box::new(VersionedXcm::from(msg)), + max_weight, + }); + } + + let mut block_builder = client.init_polkadot_block_builder(); + + let execute = construct_extrinsic(&client, call, sp_keyring::Sr25519Keyring::Alice, 0); + + block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic"); + + let block = block_builder.build().expect("Finalizes the block").block; + let block_hash = block.hash(); + + futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block)) + .expect("imports the block"); + + client.state_at(block_hash).expect("state should exist").inspect_state(|| { + assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!( + r.event, + polkadot_test_runtime::RuntimeEvent::Xcm(pallet_xcm::Event::Attempted( + Outcome::Incomplete(_, XcmError::ExceedsStackLimit) + )), + ))); + }); +} + #[test] fn query_response_fires() { use pallet_test_notifier::Event::*; @@ -114,15 +169,16 @@ fn query_response_fires() { let mut block_builder = client.init_polkadot_block_builder(); let response = Response::ExecutionResult(None); - let max_weight = 1_000_000; - let msg = Xcm(vec![QueryResponse { query_id, response, max_weight }]); + let max_weight = Weight::from_parts(1_000_000, 1024 * 1024); + let querier = Some(Here.into()); + let msg = Xcm(vec![QueryResponse { query_id, response, max_weight, querier }]); let msg = Box::new(VersionedXcm::from(msg)); let execute = construct_extrinsic( &client, polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute { message: msg, - max_weight: 1_000_000_000, + max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), }), sp_keyring::Sr25519Keyring::Alice, 1, @@ -147,7 +203,7 @@ fn query_response_fires() { assert_eq!( polkadot_test_runtime::Xcm::query(query_id), Some(QueryStatus::Ready { - response: VersionedResponse::V2(Response::ExecutionResult(None)), + response: VersionedResponse::V3(Response::ExecutionResult(None)), at: 2u32.into() }), ) @@ -197,14 +253,15 @@ fn query_response_elicits_handler() { let mut block_builder = client.init_polkadot_block_builder(); let response = Response::ExecutionResult(None); - let max_weight = 1_000_000; - let msg = Xcm(vec![QueryResponse { query_id, response, max_weight }]); + let max_weight = Weight::from_parts(1_000_000, 1024 * 1024); + let querier = Some(Here.into()); + let msg = Xcm(vec![QueryResponse { query_id, response, max_weight, querier }]); let execute = construct_extrinsic( &client, polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute { message: Box::new(VersionedXcm::from(msg)), - max_weight: 1_000_000_000, + max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), }), sp_keyring::Sr25519Keyring::Alice, 1, diff --git a/xcm/xcm-executor/src/assets.rs b/xcm/xcm-executor/src/assets.rs index 6ecbf0e0cf44..9d5d94722aa8 100644 --- a/xcm/xcm-executor/src/assets.rs +++ b/xcm/xcm-executor/src/assets.rs @@ -23,9 +23,9 @@ use sp_std::{ use xcm::latest::{ AssetId, AssetInstance, Fungibility::{Fungible, NonFungible}, - MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, + InteriorMultiLocation, MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, WildFungibility::{Fungible as WildFungible, NonFungible as WildNonFungible}, - WildMultiAsset::{All, AllOf}, + WildMultiAsset::{All, AllCounted, AllOf, AllOfCounted}, }; /// List of non-wildcard fungible and non-fungible assets. @@ -60,7 +60,7 @@ impl From> for Assets { impl From for Assets { fn from(assets: MultiAssets) -> Assets { - assets.drain().into() + assets.into_inner().into() } } @@ -103,14 +103,14 @@ impl Assets { pub fn fungible_assets_iter(&self) -> impl Iterator + '_ { self.fungible .iter() - .map(|(id, &amount)| MultiAsset { fun: Fungible(amount), id: id.clone() }) + .map(|(id, &amount)| MultiAsset { fun: Fungible(amount), id: *id }) } /// A borrowing iterator over the non-fungible assets. pub fn non_fungible_assets_iter(&self) -> impl Iterator + '_ { self.non_fungible .iter() - .map(|(id, instance)| MultiAsset { fun: NonFungible(instance.clone()), id: id.clone() }) + .map(|(id, instance)| MultiAsset { fun: NonFungible(*instance), id: *id }) } /// A consuming iterator over all assets. @@ -210,20 +210,20 @@ impl Assets { } /// Mutate the assets to be interpreted as the same assets from the perspective of a `target` - /// chain. The local chain's `ancestry` is provided. + /// chain. The local chain's `context` is provided. /// /// Any assets which were unable to be reanchored are introduced into `failed_bin`. pub fn reanchor( &mut self, target: &MultiLocation, - ancestry: &MultiLocation, + context: InteriorMultiLocation, mut maybe_failed_bin: Option<&mut Self>, ) { let mut fungible = Default::default(); mem::swap(&mut self.fungible, &mut fungible); self.fungible = fungible .into_iter() - .filter_map(|(mut id, amount)| match id.reanchor(target, ancestry) { + .filter_map(|(mut id, amount)| match id.reanchor(target, context) { Ok(()) => Some((id, amount)), Err(()) => { maybe_failed_bin.as_mut().map(|f| f.fungible.insert(id, amount)); @@ -235,7 +235,7 @@ impl Assets { mem::swap(&mut self.non_fungible, &mut non_fungible); self.non_fungible = non_fungible .into_iter() - .filter_map(|(mut class, inst)| match class.reanchor(target, ancestry) { + .filter_map(|(mut class, inst)| match class.reanchor(target, context) { Ok(()) => Some((class, inst)), Err(()) => { maybe_failed_bin.as_mut().map(|f| f.non_fungible.insert((class, inst))); @@ -245,18 +245,42 @@ impl Assets { .collect(); } + /// Returns `true` if `asset` is contained within `self`. + pub fn contains_asset(&self, asset: &MultiAsset) -> bool { + match asset { + MultiAsset { fun: Fungible(amount), id } => + self.fungible.get(id).map_or(false, |a| a >= amount), + MultiAsset { fun: NonFungible(instance), id } => + self.non_fungible.contains(&(*id, *instance)), + } + } + + /// Returns `true` if all `assets` are contained within `self`. + pub fn contains_assets(&self, assets: &MultiAssets) -> bool { + assets.inner().iter().all(|a| self.contains_asset(a)) + } + + /// Returns `true` if all `assets` are contained within `self`. + pub fn contains(&self, assets: &Assets) -> bool { + assets + .fungible + .iter() + .all(|(k, v)| self.fungible.get(k).map_or(false, |a| a >= v)) && + self.non_fungible.is_superset(&assets.non_fungible) + } + /// Returns an error unless all `assets` are contained in `self`. In the case of an error, the first asset in /// `assets` which is not wholly in `self` is returned. pub fn ensure_contains(&self, assets: &MultiAssets) -> Result<(), TakeError> { for asset in assets.inner().iter() { match asset { - MultiAsset { fun: Fungible(ref amount), ref id } => { + MultiAsset { fun: Fungible(amount), id } => { if self.fungible.get(id).map_or(true, |a| a < amount) { - return Err(TakeError::AssetUnderflow((id.clone(), *amount).into())) + return Err(TakeError::AssetUnderflow((*id, *amount).into())) } }, - MultiAsset { fun: NonFungible(ref instance), ref id } => { - let id_instance = (id.clone(), instance.clone()); + MultiAsset { fun: NonFungible(instance), id } => { + let id_instance = (*id, *instance); if !self.non_fungible.contains(&id_instance) { return Err(TakeError::AssetUnderflow(id_instance.into())) } @@ -272,7 +296,8 @@ impl Assets { /// reducing it by assets it does not contain. In this case, the function is infallible. If `saturate` is `false` /// and `mask` references a definite asset which `self` does not contain then an error is returned. /// - /// The number of unique assets which are removed will never be any greater than `limit`. + /// The number of unique assets which are removed will respect the `count` parameter in the + /// counted wildcard variants. /// /// Returns `Ok` with the definite assets token from `self` and mutates `self` to its value minus /// `mask`. Returns `Err` in the non-saturating case where `self` did not contain (enough of) a definite asset to @@ -281,17 +306,18 @@ impl Assets { &mut self, mask: MultiAssetFilter, saturate: bool, - limit: usize, ) -> Result { let mut taken = Assets::new(); + let maybe_limit = mask.limit().map(|x| x as usize); match mask { - MultiAssetFilter::Wild(All) => - if self.fungible.len() + self.non_fungible.len() <= limit { + // TODO: Counted variants where we define `limit`. + MultiAssetFilter::Wild(All) | MultiAssetFilter::Wild(AllCounted(_)) => { + if maybe_limit.map_or(true, |l| self.len() <= l) { return Ok(self.swapped(Assets::new())) } else { let fungible = mem::replace(&mut self.fungible, Default::default()); fungible.into_iter().for_each(|(c, amount)| { - if taken.len() < limit { + if maybe_limit.map_or(true, |l| taken.len() < l) { taken.fungible.insert(c, amount); } else { self.fungible.insert(c, amount); @@ -299,22 +325,26 @@ impl Assets { }); let non_fungible = mem::replace(&mut self.non_fungible, Default::default()); non_fungible.into_iter().for_each(|(c, instance)| { - if taken.len() < limit { + if maybe_limit.map_or(true, |l| taken.len() < l) { taken.non_fungible.insert((c, instance)); } else { self.non_fungible.insert((c, instance)); } }); - }, - MultiAssetFilter::Wild(AllOf { fun: WildFungible, id }) => { - if let Some((id, amount)) = self.fungible.remove_entry(&id) { - taken.fungible.insert(id, amount); } }, + MultiAssetFilter::Wild(AllOfCounted { fun: WildFungible, id, .. }) | + MultiAssetFilter::Wild(AllOf { fun: WildFungible, id }) => + if maybe_limit.map_or(true, |l| l >= 1) { + if let Some((id, amount)) = self.fungible.remove_entry(&id) { + taken.fungible.insert(id, amount); + } + }, + MultiAssetFilter::Wild(AllOfCounted { fun: WildNonFungible, id, .. }) | MultiAssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => { let non_fungible = mem::replace(&mut self.non_fungible, Default::default()); non_fungible.into_iter().for_each(|(c, instance)| { - if c == id && taken.len() < limit { + if c == id && maybe_limit.map_or(true, |l| taken.len() < l) { taken.non_fungible.insert((c, instance)); } else { self.non_fungible.insert((c, instance)); @@ -325,7 +355,7 @@ impl Assets { if !saturate { self.ensure_contains(&assets)?; } - for asset in assets.drain().into_iter() { + for asset in assets.into_inner().into_iter() { match asset { MultiAsset { fun: Fungible(amount), id } => { let (remove, amount) = match self.fungible.get_mut(&id) { @@ -350,9 +380,6 @@ impl Assets { } }, } - if taken.len() == limit { - break - } } }, } @@ -364,16 +391,7 @@ impl Assets { /// Returns `Ok` with the non-wildcard equivalence of `mask` taken and mutates `self` to its value minus /// `mask` if `self` contains `asset`, and return `Err` otherwise. pub fn saturating_take(&mut self, asset: MultiAssetFilter) -> Assets { - self.general_take(asset, true, usize::max_value()) - .expect("general_take never results in error when saturating") - } - - /// Mutates `self` to its original value less `mask` and returns `true` iff it contains at least `mask`. - /// - /// Returns `Ok` with the non-wildcard equivalence of `mask` taken and mutates `self` to its value minus - /// `mask` if `self` contains `asset`, and return `Err` otherwise. - pub fn limited_saturating_take(&mut self, asset: MultiAssetFilter, limit: usize) -> Assets { - self.general_take(asset, true, limit) + self.general_take(asset, true) .expect("general_take never results in error when saturating") } @@ -382,7 +400,7 @@ impl Assets { /// Returns `Ok` with the non-wildcard equivalence of `asset` taken and mutates `self` to its value minus /// `asset` if `self` contains `asset`, and return `Err` otherwise. pub fn try_take(&mut self, mask: MultiAssetFilter) -> Result { - self.general_take(mask, false, usize::max_value()) + self.general_take(mask, false) } /// Consumes `self` and returns its original value excluding `asset` iff it contains at least `asset`. @@ -415,47 +433,72 @@ impl Assets { /// Return the assets in `self`, but (asset-wise) of no greater value than `mask`. /// - /// Result is undefined if `mask` includes elements which match to the same asset more than once. + /// The number of unique assets which are returned will respect the `count` parameter in the + /// counted wildcard variants of `mask`. /// /// Example: /// /// ``` /// use xcm_executor::Assets; /// use xcm::latest::prelude::*; - /// let assets_i_have: Assets = vec![ (Here, 100).into(), (vec![0], 100).into() ].into(); - /// let assets_they_want: MultiAssetFilter = vec![ (Here, 200).into(), (vec![0], 50).into() ].into(); + /// let assets_i_have: Assets = vec![ (Here, 100).into(), ([0; 32], 100).into() ].into(); + /// let assets_they_want: MultiAssetFilter = vec![ (Here, 200).into(), ([0; 32], 50).into() ].into(); /// /// let assets_we_can_trade: Assets = assets_i_have.min(&assets_they_want); /// assert_eq!(assets_we_can_trade.into_assets_iter().collect::>(), vec![ - /// (Here, 100).into(), (vec![0], 50).into(), + /// (Here, 100).into(), ([0; 32], 50).into(), /// ]); /// ``` pub fn min(&self, mask: &MultiAssetFilter) -> Assets { let mut masked = Assets::new(); + let maybe_limit = mask.limit().map(|x| x as usize); + if maybe_limit.map_or(false, |l| l == 0) { + return masked + } match mask { - MultiAssetFilter::Wild(All) => return self.clone(), - MultiAssetFilter::Wild(AllOf { fun: WildFungible, id }) => { - if let Some(&amount) = self.fungible.get(&id) { - masked.fungible.insert(id.clone(), amount); + MultiAssetFilter::Wild(All) | MultiAssetFilter::Wild(AllCounted(_)) => { + if maybe_limit.map_or(true, |l| self.len() <= l) { + return self.clone() + } else { + for (&c, &amount) in self.fungible.iter() { + masked.fungible.insert(c, amount); + if maybe_limit.map_or(false, |l| masked.len() >= l) { + return masked + } + } + for (c, instance) in self.non_fungible.iter() { + masked.non_fungible.insert((*c, *instance)); + if maybe_limit.map_or(false, |l| masked.len() >= l) { + return masked + } + } } }, - MultiAssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => { - self.non_fungible.iter().for_each(|(ref c, ref instance)| { + MultiAssetFilter::Wild(AllOfCounted { fun: WildFungible, id, .. }) | + MultiAssetFilter::Wild(AllOf { fun: WildFungible, id }) => + if let Some(&amount) = self.fungible.get(&id) { + masked.fungible.insert(*id, amount); + }, + MultiAssetFilter::Wild(AllOfCounted { fun: WildNonFungible, id, .. }) | + MultiAssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => + for (c, instance) in self.non_fungible.iter() { if c == id { - masked.non_fungible.insert((c.clone(), instance.clone())); + masked.non_fungible.insert((*c, *instance)); + if maybe_limit.map_or(false, |l| masked.len() >= l) { + return masked + } } - }); - }, + }, MultiAssetFilter::Definite(assets) => for asset in assets.inner().iter() { match asset { - MultiAsset { fun: Fungible(ref amount), ref id } => { + MultiAsset { fun: Fungible(amount), id } => { if let Some(m) = self.fungible.get(id) { - masked.subsume((id.clone(), Fungible(*amount.min(m))).into()); + masked.subsume((*id, Fungible(*amount.min(m))).into()); } }, - MultiAsset { fun: NonFungible(ref instance), ref id } => { - let id_instance = (id.clone(), instance.clone()); + MultiAsset { fun: NonFungible(instance), id } => { + let id_instance = (*id, *instance); if self.non_fungible.contains(&id_instance) { masked.subsume(id_instance.into()); } @@ -474,12 +517,12 @@ mod tests { #[allow(non_snake_case)] /// Abstract fungible constructor fn AF(id: u8, amount: u128) -> MultiAsset { - (vec![id], amount).into() + ([id; 32], amount).into() } #[allow(non_snake_case)] /// Abstract non-fungible constructor fn ANF(class: u8, instance_id: u8) -> MultiAsset { - (vec![class], vec![instance_id]).into() + ([class; 32], [instance_id; 4]).into() } #[allow(non_snake_case)] /// Concrete fungible constructor @@ -586,11 +629,38 @@ mod tests { assert!(all_min.assets_iter().eq(assets.assets_iter())); } + #[test] + fn min_counted_works() { + let mut assets = Assets::new(); + assets.subsume(AF(1, 100)); + assets.subsume(ANF(2, 20)); + assets.subsume(CNF(40)); + assets.subsume(AF(10, 50)); + assets.subsume(ANF(2, 40)); + assets.subsume(ANF(2, 30)); + assets.subsume(CF(3000)); + assets.subsume(CNF(80)); + assets.subsume(ANF(3, 10)); + let fungible = WildMultiAsset::from(([1u8; 32], WildFungible)).counted(2).into(); + let non_fungible = WildMultiAsset::from(([2u8; 32], WildNonFungible)).counted(2).into(); + let all = WildMultiAsset::AllCounted(6).into(); + + let fungible = assets.min(&fungible); + let fungible = fungible.assets_iter().collect::>(); + assert_eq!(fungible, vec![AF(1, 100)]); + let non_fungible = assets.min(&non_fungible); + let non_fungible = non_fungible.assets_iter().collect::>(); + assert_eq!(non_fungible, vec![ANF(2, 20), ANF(2, 30)]); + let all = assets.min(&all); + let all = all.assets_iter().collect::>(); + assert_eq!(all, vec![CF(3000), AF(1, 100), AF(10, 50), CNF(40), CNF(80), ANF(2, 20),]); + } + #[test] fn min_all_abstract_works() { let assets = test_assets(); - let fungible = Wild((vec![1], WildFungible).into()); - let non_fungible = Wild((vec![2], WildNonFungible).into()); + let fungible = Wild(([1u8; 32], WildFungible).into()); + let non_fungible = Wild(([2u8; 32], WildNonFungible).into()); let fungible = assets.min(&fungible); let fungible = fungible.assets_iter().collect::>(); @@ -650,8 +720,8 @@ mod tests { #[test] fn saturating_take_all_abstract_works() { let mut assets = test_assets(); - let fungible = Wild((vec![1], WildFungible).into()); - let non_fungible = Wild((vec![2], WildNonFungible).into()); + let fungible = Wild(([1u8; 32], WildFungible).into()); + let non_fungible = Wild(([2u8; 32], WildNonFungible).into()); let fungible = assets.saturating_take(fungible); let fungible = fungible.assets_iter().collect::>(); @@ -703,4 +773,75 @@ mod tests { let assets = assets1.into_assets_iter().collect::>(); assert_eq!(assets, vec![AF(1, 50), ANF(2, 20)]); } + + #[test] + fn try_take_all_counted_works() { + let mut assets = Assets::new(); + assets.subsume(AF(1, 100)); + assets.subsume(ANF(2, 20)); + assets.subsume(CNF(40)); + assets.subsume(AF(10, 50)); + assets.subsume(ANF(2, 40)); + assets.subsume(ANF(2, 30)); + assets.subsume(CF(3000)); + assets.subsume(CNF(80)); + assets.subsume(ANF(3, 10)); + let all = assets.try_take(WildMultiAsset::AllCounted(6).into()).unwrap(); + assert_eq!( + MultiAssets::from(all).inner(), + &vec![CF(3000), AF(1, 100), AF(10, 50), CNF(40), CNF(80), ANF(2, 20),] + ); + assert_eq!(MultiAssets::from(assets).inner(), &vec![ANF(2, 30), ANF(2, 40), ANF(3, 10),]); + } + + #[test] + fn try_take_fungibles_counted_works() { + let mut assets = Assets::new(); + assets.subsume(AF(1, 100)); + assets.subsume(ANF(2, 20)); + assets.subsume(CNF(40)); + assets.subsume(AF(10, 50)); + assets.subsume(ANF(2, 40)); + assets.subsume(ANF(2, 30)); + assets.subsume(CF(3000)); + assets.subsume(CNF(80)); + assets.subsume(ANF(3, 10)); + let mask = WildMultiAsset::from(([1u8; 32], WildFungible)).counted(2).into(); + let taken = assets.try_take(mask).unwrap(); + assert_eq!(MultiAssets::from(taken).inner(), &vec![AF(1, 100)]); + assert_eq!( + MultiAssets::from(assets).inner(), + &vec![ + CF(3000), + AF(10, 50), + CNF(40), + CNF(80), + ANF(2, 20), + ANF(2, 30), + ANF(2, 40), + ANF(3, 10), + ] + ); + } + + #[test] + fn try_take_non_fungibles_counted_works() { + let mut assets = Assets::new(); + assets.subsume(AF(1, 100)); + assets.subsume(ANF(2, 20)); + assets.subsume(CNF(40)); + assets.subsume(AF(10, 50)); + assets.subsume(ANF(2, 40)); + assets.subsume(ANF(2, 30)); + assets.subsume(CF(3000)); + assets.subsume(CNF(80)); + assets.subsume(ANF(3, 10)); + let mask = WildMultiAsset::from(([2u8; 32], WildNonFungible)).counted(2).into(); + let taken = assets.try_take(mask).unwrap(); + assert_eq!(MultiAssets::from(taken).inner(), &vec![ANF(2, 20), ANF(2, 30),]); + assert_eq!( + MultiAssets::from(assets).inner(), + &vec![CF(3000), AF(1, 100), AF(10, 50), CNF(40), CNF(80), ANF(2, 40), ANF(3, 10),] + ); + } } diff --git a/xcm/xcm-executor/src/config.rs b/xcm/xcm-executor/src/config.rs index 8b15ac3b754f..9bb98055fb20 100644 --- a/xcm/xcm-executor/src/config.rs +++ b/xcm/xcm-executor/src/config.rs @@ -15,11 +15,15 @@ // along with Polkadot. If not, see . use crate::traits::{ - ClaimAssets, ConvertOrigin, DropAssets, FilterAssetLocation, InvertLocation, OnResponse, - ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, + AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, DropAssets, ExportXcm, + FeeManager, OnResponse, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, + WeightTrader, }; -use frame_support::dispatch::{Dispatchable, GetDispatchInfo, Parameter, PostDispatchInfo}; -use xcm::latest::SendXcm; +use frame_support::{ + dispatch::{Dispatchable, GetDispatchInfo, Parameter, PostDispatchInfo}, + traits::{Contains, ContainsPair, Get, PalletsInfoAccess}, +}; +use xcm::prelude::*; /// The trait to parameterize the `XcmExecutor`. pub trait Config { @@ -35,14 +39,14 @@ pub trait Config { /// How to get a call origin from a `OriginKind` value. type OriginConverter: ConvertOrigin<::RuntimeOrigin>; - /// Combinations of (Location, Asset) pairs which we trust as reserves. - type IsReserve: FilterAssetLocation; + /// Combinations of (Asset, Location) pairs which we trust as reserves. + type IsReserve: ContainsPair; - /// Combinations of (Location, Asset) pairs which we trust as teleporters. - type IsTeleporter: FilterAssetLocation; + /// Combinations of (Asset, Location) pairs which we trust as teleporters. + type IsTeleporter: ContainsPair; - /// Means of inverting a location. - type LocationInverter: InvertLocation; + /// This chain's Universal Location. + type UniversalLocation: Get; /// Whether we should execute the given XCM at all. type Barrier: ShouldExecute; @@ -60,9 +64,46 @@ pub trait Config { /// end of execution. type AssetTrap: DropAssets; + /// Handler for asset locking. + type AssetLocker: AssetLock; + + /// Handler for exchanging assets. + type AssetExchanger: AssetExchange; + /// The handler for when there is an instruction to claim assets. type AssetClaims: ClaimAssets; /// How we handle version subscription requests. type SubscriptionService: VersionChangeNotifier; + + /// Information on all pallets. + type PalletInstancesInfo: PalletsInfoAccess; + + /// The maximum number of assets we target to have in the Holding Register at any one time. + /// + /// NOTE: In the worse case, the Holding Register may contain up to twice as many assets as this + /// and any benchmarks should take that into account. + type MaxAssetsIntoHolding: Get; + + /// Configure the fees. + type FeeManager: FeeManager; + + /// The method of exporting a message. + type MessageExporter: ExportXcm; + + /// The origin locations and specific universal junctions to which they are allowed to elevate + /// themselves. + type UniversalAliases: Contains<(MultiLocation, Junction)>; + + /// The call dispatcher used by XCM. + /// + /// XCM will use this to dispatch any calls. When no special call dispatcher is required, + /// this can be set to the same type as `Self::Call`. + type CallDispatcher: CallDispatcher; + + /// The safe call filter for `Transact`. + /// + /// Use this type to explicitly whitelist calls that cannot undergo recursion. This is a + /// temporary measure until we properly account for proof size weights for XCM instructions. + type SafeCallFilter: Contains; } diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index be2bd0c9791f..175f9e54fae7 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -17,21 +17,22 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::{ - dispatch::{Dispatchable, GetDispatchInfo}, + dispatch::GetDispatchInfo, ensure, + traits::{Contains, ContainsPair, Get, PalletsInfoAccess}, }; -use sp_runtime::traits::Saturating; +use parity_scale_codec::{Decode, Encode}; +use sp_core::defer; +use sp_io::hashing::blake2_128; use sp_std::{marker::PhantomData, prelude::*}; -use xcm::latest::{ - Error as XcmError, ExecuteXcm, - Instruction::{self, *}, - MultiAssets, MultiLocation, Outcome, Response, SendXcm, Weight, Xcm, -}; +use sp_weights::Weight; +use xcm::latest::prelude::*; pub mod traits; use traits::{ - ClaimAssets, ConvertOrigin, DropAssets, FilterAssetLocation, InvertLocation, OnResponse, - ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, + validate_export, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, + DropAssets, Enact, ExportXcm, FeeManager, FeeReason, OnResponse, ShouldExecute, TransactAsset, + VersionChangeNotifier, WeightBounds, WeightTrader, }; mod assets; @@ -39,94 +40,191 @@ pub use assets::Assets; mod config; pub use config::Config; +/// A struct to specify how fees are being paid. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct FeesMode { + /// If true, then the fee assets are taken directly from the origin's on-chain account, + /// otherwise the fee assets are taken from the holding register. + /// + /// Defaults to false. + pub jit_withdraw: bool, +} + +const RECURSION_LIMIT: u8 = 10; + +environmental::environmental!(recursion_count: u8); + /// The XCM executor. pub struct XcmExecutor { - pub holding: Assets, - pub origin: Option, - pub original_origin: MultiLocation, - pub trader: Config::Trader, + holding: Assets, + holding_limit: usize, + context: XcmContext, + original_origin: MultiLocation, + trader: Config::Trader, /// The most recent error result and instruction index into the fragment in which it occurred, /// if any. - pub error: Option<(u32, XcmError)>, + error: Option<(u32, XcmError)>, /// The surplus weight, defined as the amount by which `max_weight` is /// an over-estimate of the actual weight consumed. We do it this way to avoid needing the /// execution engine to keep track of all instructions' weights (it only needs to care about /// the weight of dynamically determined instructions such as `Transact`). - pub total_surplus: u64, - pub total_refunded: u64, - pub error_handler: Xcm, - pub error_handler_weight: u64, - pub appendix: Xcm, - pub appendix_weight: u64, + total_surplus: Weight, + total_refunded: Weight, + error_handler: Xcm, + error_handler_weight: Weight, + appendix: Xcm, + appendix_weight: Weight, + transact_status: MaybeErrorCode, + fees_mode: FeesMode, _config: PhantomData, } -/// The maximum recursion limit for `execute_xcm` and `execute_effects`. -pub const MAX_RECURSION_LIMIT: u32 = 8; +#[cfg(feature = "runtime-benchmarks")] +impl XcmExecutor { + pub fn holding(&self) -> &Assets { + &self.holding + } + pub fn set_holding(&mut self, v: Assets) { + self.holding = v + } + pub fn holding_limit(&self) -> &usize { + &self.holding_limit + } + pub fn set_holding_limit(&mut self, v: usize) { + self.holding_limit = v + } + pub fn origin(&self) -> &Option { + &self.context.origin + } + pub fn set_origin(&mut self, v: Option) { + self.context.origin = v + } + pub fn original_origin(&self) -> &MultiLocation { + &self.original_origin + } + pub fn set_original_origin(&mut self, v: MultiLocation) { + self.original_origin = v + } + pub fn trader(&self) -> &Config::Trader { + &self.trader + } + pub fn set_trader(&mut self, v: Config::Trader) { + self.trader = v + } + pub fn error(&self) -> &Option<(u32, XcmError)> { + &self.error + } + pub fn set_error(&mut self, v: Option<(u32, XcmError)>) { + self.error = v + } + pub fn total_surplus(&self) -> &Weight { + &self.total_surplus + } + pub fn set_total_surplus(&mut self, v: Weight) { + self.total_surplus = v + } + pub fn total_refunded(&self) -> &Weight { + &self.total_refunded + } + pub fn set_total_refunded(&mut self, v: Weight) { + self.total_refunded = v + } + pub fn error_handler(&self) -> &Xcm { + &self.error_handler + } + pub fn set_error_handler(&mut self, v: Xcm) { + self.error_handler = v + } + pub fn error_handler_weight(&self) -> &Weight { + &self.error_handler_weight + } + pub fn set_error_handler_weight(&mut self, v: Weight) { + self.error_handler_weight = v + } + pub fn appendix(&self) -> &Xcm { + &self.appendix + } + pub fn set_appendix(&mut self, v: Xcm) { + self.appendix = v + } + pub fn appendix_weight(&self) -> &Weight { + &self.appendix_weight + } + pub fn set_appendix_weight(&mut self, v: Weight) { + self.appendix_weight = v + } + pub fn transact_status(&self) -> &MaybeErrorCode { + &self.transact_status + } + pub fn set_transact_status(&mut self, v: MaybeErrorCode) { + self.transact_status = v + } + pub fn fees_mode(&self) -> &FeesMode { + &self.fees_mode + } + pub fn set_fees_mode(&mut self, v: FeesMode) { + self.fees_mode = v + } + pub fn topic(&self) -> &Option<[u8; 32]> { + &self.context.topic + } + pub fn set_topic(&mut self, v: Option<[u8; 32]>) { + self.context.topic = v; + } +} + +pub struct WeighedMessage(Weight, Xcm); +impl PreparedMessage for WeighedMessage { + fn weight_of(&self) -> Weight { + self.0 + } +} impl ExecuteXcm for XcmExecutor { - fn execute_xcm_in_credit( - origin: impl Into, + type Prepared = WeighedMessage; + fn prepare( mut message: Xcm, - weight_limit: Weight, + ) -> Result> { + match Config::Weigher::weight(&mut message) { + Ok(weight) => Ok(WeighedMessage(weight, message)), + Err(_) => Err(message), + } + } + fn execute( + origin: impl Into, + WeighedMessage(xcm_weight, mut message): WeighedMessage, + message_hash: XcmHash, mut weight_credit: Weight, ) -> Outcome { let origin = origin.into(); log::trace!( target: "xcm::execute_xcm_in_credit", - "origin: {:?}, message: {:?}, weight_limit: {:?}, weight_credit: {:?}", + "origin: {:?}, message: {:?}, weight_credit: {:?}", origin, message, - weight_limit, weight_credit, ); - let xcm_weight = match Config::Weigher::weight(&mut message) { - Ok(x) => x, - Err(()) => { - log::debug!( - target: "xcm::execute_xcm_in_credit", - "Weight not computable! (origin: {:?}, message: {:?}, weight_limit: {:?}, weight_credit: {:?})", - origin, - message, - weight_limit, - weight_credit, - ); - return Outcome::Error(XcmError::WeightNotComputable) - }, - }; - if xcm_weight > weight_limit { - log::debug!( + if let Err(e) = Config::Barrier::should_execute( + &origin, + message.inner_mut(), + xcm_weight, + &mut weight_credit, + ) { + log::trace!( target: "xcm::execute_xcm_in_credit", - "Weight limit reached! weight > weight_limit: {:?} > {:?}. (origin: {:?}, message: {:?}, weight_limit: {:?}, weight_credit: {:?})", - xcm_weight, - weight_limit, - origin, - message, - weight_limit, - weight_credit, - ); - return Outcome::Error(XcmError::WeightLimitReached(xcm_weight)) - } - - if let Err(e) = - Config::Barrier::should_execute(&origin, &mut message, xcm_weight, &mut weight_credit) - { - log::debug!( - target: "xcm::execute_xcm_in_credit", - "Barrier blocked execution! Error: {:?}. (origin: {:?}, message: {:?}, weight_limit: {:?}, weight_credit: {:?})", + "Barrier blocked execution! Error: {:?}. (origin: {:?}, message: {:?}, weight_credit: {:?})", e, origin, message, - weight_limit, weight_credit, ); return Outcome::Error(XcmError::Barrier) } - let mut vm = Self::new(origin); + let mut vm = Self::new(origin, message_hash); while !message.0.is_empty() { - let result = vm.execute(message); + let result = vm.process(message); log::trace!(target: "xcm::execute_xcm_in_credit", "result: {:?}", result); message = if let Err(error) = result { vm.total_surplus.saturating_accrue(error.weight); @@ -138,7 +236,18 @@ impl ExecuteXcm for XcmExecutor, fees: MultiAssets) -> XcmResult { + let origin = origin.into(); + if !Config::FeeManager::is_waived(Some(&origin), FeeReason::ChargeFees) { + for asset in fees.inner() { + Config::AssetTransactor::withdraw_asset(&asset, &origin, None)?; + } + Config::FeeManager::handle_fee(fees); + } + Ok(()) } } @@ -146,7 +255,7 @@ impl ExecuteXcm for XcmExecutor for frame_benchmarking::BenchmarkError { } impl XcmExecutor { - pub fn new(origin: impl Into) -> Self { + pub fn new(origin: impl Into, message_hash: XcmHash) -> Self { let origin = origin.into(); Self { holding: Assets::new(), - origin: Some(origin.clone()), + holding_limit: Config::MaxAssetsIntoHolding::get() as usize, + context: XcmContext { origin: Some(origin), message_hash, topic: None }, original_origin: origin, trader: Config::Trader::new(), error: None, - total_surplus: 0, - total_refunded: 0, + total_surplus: Weight::zero(), + total_refunded: Weight::zero(), error_handler: Xcm(vec![]), - error_handler_weight: 0, + error_handler_weight: Weight::zero(), appendix: Xcm(vec![]), - appendix_weight: 0, + appendix_weight: Weight::zero(), + transact_status: Default::default(), + fees_mode: FeesMode { jit_withdraw: false }, _config: PhantomData, } } - /// Execute the XCM program fragment and report back the error and which instruction caused it, - /// or `Ok` if there was no error. - pub fn execute(&mut self, xcm: Xcm) -> Result<(), ExecutorError> { + #[cfg(feature = "runtime-benchmarks")] + pub fn bench_process(&mut self, xcm: Xcm) -> Result<(), ExecutorError> { + self.process(xcm) + } + + fn process(&mut self, xcm: Xcm) -> Result<(), ExecutorError> { log::trace!( - target: "xcm::execute", + target: "xcm::process", "origin: {:?}, total_surplus/refunded: {:?}/{:?}, error_handler_weight: {:?}", - self.origin, + self.origin_ref(), self.total_surplus, self.total_refunded, self.error_handler_weight, @@ -195,10 +310,39 @@ impl XcmExecutor { let mut result = Ok(()); for (i, instr) in xcm.0.into_iter().enumerate() { match &mut result { - r @ Ok(()) => - if let Err(e) = self.process_instruction(instr) { - *r = Err(ExecutorError { index: i as u32, xcm_error: e, weight: 0 }); - }, + r @ Ok(()) => { + // Initialize the recursion count only the first time we hit this code in our + // potential recursive execution. + let inst_res = recursion_count::using_once(&mut 1, || { + recursion_count::with(|count| { + if *count > RECURSION_LIMIT { + return Err(XcmError::ExceedsStackLimit) + } + *count = count.saturating_add(1); + Ok(()) + }) + // This should always return `Some`, but let's play it safe. + .unwrap_or(Ok(()))?; + + // Ensure that we always decrement the counter whenever we finish processing + // the instruction. + defer! { + recursion_count::with(|count| { + *count = count.saturating_sub(1); + }); + } + + self.process_instruction(instr) + }); + if let Err(e) = inst_res { + log::trace!(target: "xcm::execute", "!!! ERROR: {:?}", e); + *r = Err(ExecutorError { + index: i as u32, + xcm_error: e, + weight: Weight::zero(), + }); + } + }, Err(ref mut error) => if let Ok(x) = Config::Weigher::instr_weight(&instr) { error.weight.saturating_accrue(x) @@ -210,15 +354,23 @@ impl XcmExecutor { /// Execute any final operations after having executed the XCM message. /// This includes refunding surplus weight, trapping extra holding funds, and returning any errors during execution. - pub fn post_execute(mut self, xcm_weight: Weight) -> Outcome { - self.refund_surplus(); + pub fn post_process(mut self, xcm_weight: Weight) -> Outcome { + // We silently drop any error from our attempt to refund the surplus as it's a charitable + // thing so best-effort is all we will do. + let _ = self.refund_surplus(); drop(self.trader); let mut weight_used = xcm_weight.saturating_sub(self.total_surplus); if !self.holding.is_empty() { - log::trace!(target: "xcm::execute_xcm_in_credit", "Trapping assets in holding register: {:?} (original_origin: {:?})", self.holding, self.original_origin); - let trap_weight = Config::AssetTrap::drop_assets(&self.original_origin, self.holding); + log::trace!( + target: "xcm::execute_xcm_in_credit", + "Trapping assets in holding register: {:?}, context: {:?} (original_origin: {:?})", + self.holding, self.context, self.original_origin, + ); + let effective_origin = self.context.origin.as_ref().unwrap_or(&self.original_origin); + let trap_weight = + Config::AssetTrap::drop_assets(effective_origin, self.holding, &self.context); weight_used.saturating_accrue(trap_weight); }; @@ -227,17 +379,40 @@ impl XcmExecutor { // TODO: #2841 #REALWEIGHT We should deduct the cost of any instructions following // the error which didn't end up being executed. Some((_i, e)) => { - log::debug!(target: "xcm::execute_xcm_in_credit", "Execution errored at {:?}: {:?} (original_origin: {:?})", _i, e, self.original_origin); + log::trace!(target: "xcm::execute_xcm_in_credit", "Execution errored at {:?}: {:?} (original_origin: {:?})", _i, e, self.original_origin); Outcome::Incomplete(weight_used, e) }, } } + fn origin_ref(&self) -> Option<&MultiLocation> { + self.context.origin.as_ref() + } + + fn cloned_origin(&self) -> Option { + self.context.origin + } + + /// Send an XCM, charging fees from Holding as needed. + fn send( + &mut self, + dest: MultiLocation, + msg: Xcm<()>, + reason: FeeReason, + ) -> Result { + let (ticket, fee) = validate_send::(dest, msg)?; + if !Config::FeeManager::is_waived(self.origin_ref(), reason) { + let paid = self.holding.try_take(fee.into()).map_err(|_| XcmError::NotHoldingFees)?; + Config::FeeManager::handle_fee(paid.into()); + } + Config::XcmSender::deliver(ticket).map_err(Into::into) + } + /// Remove the registered error handler and return it. Do not refund its weight. fn take_error_handler(&mut self) -> Xcm { let mut r = Xcm::(vec![]); sp_std::mem::swap(&mut self.error_handler, &mut r); - self.error_handler_weight = 0; + self.error_handler_weight = Weight::zero(); r } @@ -245,26 +420,45 @@ impl XcmExecutor { fn drop_error_handler(&mut self) { self.error_handler = Xcm::(vec![]); self.total_surplus.saturating_accrue(self.error_handler_weight); - self.error_handler_weight = 0; + self.error_handler_weight = Weight::zero(); } /// Remove the registered appendix and return it. fn take_appendix(&mut self) -> Xcm { let mut r = Xcm::(vec![]); sp_std::mem::swap(&mut self.appendix, &mut r); - self.appendix_weight = 0; + self.appendix_weight = Weight::zero(); r } + fn subsume_asset(&mut self, asset: MultiAsset) -> Result<(), XcmError> { + // worst-case, holding.len becomes 2 * holding_limit. + ensure!(self.holding.len() < self.holding_limit * 2, XcmError::HoldingWouldOverflow); + self.holding.subsume(asset); + Ok(()) + } + + fn subsume_assets(&mut self, assets: Assets) -> Result<(), XcmError> { + // worst-case, holding.len becomes 2 * holding_limit. + // this guarantees that if holding.len() == holding_limit and you have holding_limit more + // items (which has a best case outcome of holding.len() == holding_limit), then you'll + // be guaranteed of making the operation. + let worst_case_holding_len = self.holding.len() + assets.len(); + ensure!(worst_case_holding_len <= self.holding_limit * 2, XcmError::HoldingWouldOverflow); + self.holding.subsume_assets(assets); + Ok(()) + } + /// Refund any unused weight. - fn refund_surplus(&mut self) { + fn refund_surplus(&mut self) -> Result<(), XcmError> { let current_surplus = self.total_surplus.saturating_sub(self.total_refunded); - if current_surplus > 0 { + if current_surplus.any_gt(Weight::zero()) { self.total_refunded.saturating_accrue(current_surplus); if let Some(w) = self.trader.refund_weight(current_surplus) { - self.holding.subsume(w); + self.subsume_asset(w)?; } } + Ok(()) } /// Process a single XCM instruction, mutating the state of the XCM virtual machine. @@ -272,89 +466,104 @@ impl XcmExecutor { &mut self, instr: Instruction, ) -> Result<(), XcmError> { + log::trace!( + target: "xcm::process_instruction", + "=== {:?}", + instr + ); match instr { WithdrawAsset(assets) => { // Take `assets` from the origin account (on-chain) and place in holding. - let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?; - for asset in assets.drain().into_iter() { - Config::AssetTransactor::withdraw_asset(&asset, origin)?; - self.holding.subsume(asset); + let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + for asset in assets.into_inner().into_iter() { + Config::AssetTransactor::withdraw_asset(&asset, &origin, Some(&self.context))?; + self.subsume_asset(asset)?; } Ok(()) }, ReserveAssetDeposited(assets) => { // check whether we trust origin to be our reserve location for this asset. - let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?; - for asset in assets.drain().into_iter() { + let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + for asset in assets.into_inner().into_iter() { // Must ensure that we recognise the asset as being managed by the origin. ensure!( - Config::IsReserve::filter_asset_location(&asset, origin), + Config::IsReserve::contains(&asset, &origin), XcmError::UntrustedReserveLocation ); - self.holding.subsume(asset); + self.subsume_asset(asset)?; } Ok(()) }, TransferAsset { assets, beneficiary } => { // Take `assets` from the origin account (on-chain) and place into dest account. - let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?; + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; for asset in assets.inner() { - Config::AssetTransactor::transfer_asset(&asset, origin, &beneficiary)?; + Config::AssetTransactor::transfer_asset( + &asset, + origin, + &beneficiary, + &self.context, + )?; } Ok(()) }, TransferReserveAsset { mut assets, dest, xcm } => { - let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?; + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; // Take `assets` from the origin account (on-chain) and place into dest account. for asset in assets.inner() { - Config::AssetTransactor::transfer_asset(asset, origin, &dest)?; + Config::AssetTransactor::transfer_asset(asset, origin, &dest, &self.context)?; } - let ancestry = Config::LocationInverter::ancestry(); - assets.reanchor(&dest, &ancestry).map_err(|()| XcmError::MultiLocationFull)?; + let reanchor_context = Config::UniversalLocation::get(); + assets.reanchor(&dest, reanchor_context).map_err(|()| XcmError::LocationFull)?; let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; message.extend(xcm.0.into_iter()); - Config::XcmSender::send_xcm(dest, Xcm(message)).map_err(Into::into) + self.send(dest, Xcm(message), FeeReason::TransferReserveAsset)?; + Ok(()) }, ReceiveTeleportedAsset(assets) => { - let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?; + let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; // check whether we trust origin to teleport this asset to us via config trait. for asset in assets.inner() { // We only trust the origin to send us assets that they identify as their // sovereign assets. ensure!( - Config::IsTeleporter::filter_asset_location(asset, origin), + Config::IsTeleporter::contains(asset, &origin), XcmError::UntrustedTeleportLocation ); // We should check that the asset can actually be teleported in (for this to be in error, there // would need to be an accounting violation by one of the trusted chains, so it's unlikely, but we // don't want to punish a possibly innocent chain/user). - Config::AssetTransactor::can_check_in(&origin, asset)?; + Config::AssetTransactor::can_check_in(&origin, asset, &self.context)?; } - for asset in assets.drain().into_iter() { - Config::AssetTransactor::check_in(origin, &asset); - self.holding.subsume(asset); + for asset in assets.into_inner().into_iter() { + Config::AssetTransactor::check_in(&origin, &asset, &self.context); + self.subsume_asset(asset)?; } Ok(()) }, - Transact { origin_type, require_weight_at_most, mut call } => { + Transact { origin_kind, require_weight_at_most, mut call } => { // We assume that the Relay-chain is allowed to use transact on this parachain. - let origin = self.origin.clone().ok_or(XcmError::BadOrigin)?; + let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; // TODO: #2841 #TRANSACTFILTER allow the trait to issue filters for the relay-chain let message_call = call.take_decoded().map_err(|_| XcmError::FailedToDecode)?; - let dispatch_origin = Config::OriginConverter::convert_origin(origin, origin_type) + ensure!(Config::SafeCallFilter::contains(&message_call), XcmError::NoPermission); + let dispatch_origin = Config::OriginConverter::convert_origin(origin, origin_kind) .map_err(|_| XcmError::BadOrigin)?; let weight = message_call.get_dispatch_info().weight; - ensure!(weight.ref_time() <= require_weight_at_most, XcmError::MaxWeightInvalid); - let actual_weight = match message_call.dispatch(dispatch_origin) { - Ok(post_info) => post_info.actual_weight, - Err(error_and_info) => { - // Not much to do with the result as it is. It's up to the parachain to ensure that the - // message makes sense. - error_and_info.post_info.actual_weight - }, - } - .unwrap_or(weight); + ensure!(weight.all_lte(require_weight_at_most), XcmError::MaxWeightInvalid); + let maybe_actual_weight = + match Config::CallDispatcher::dispatch(message_call, dispatch_origin) { + Ok(post_info) => { + self.transact_status = MaybeErrorCode::Success; + post_info.actual_weight + }, + Err(error_and_info) => { + self.transact_status = error_and_info.error.encode().into(); + error_and_info.post_info.actual_weight + }, + }; + let actual_weight = maybe_actual_weight.unwrap_or(weight); let surplus = weight.saturating_sub(actual_weight); // We assume that the `Config::Weigher` will counts the `require_weight_at_most` // for the estimate of how much weight this instruction will take. Now that we know @@ -364,50 +573,62 @@ impl XcmExecutor { // reported back to the caller and this ensures that they account for the total // weight consumed correctly (potentially allowing them to do more operations in a // block than they otherwise would). - self.total_surplus.saturating_accrue(surplus.ref_time()); + self.total_surplus.saturating_accrue(surplus); Ok(()) }, - QueryResponse { query_id, response, max_weight } => { - let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?; - Config::ResponseHandler::on_response(origin, query_id, response, max_weight); + QueryResponse { query_id, response, max_weight, querier } => { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + Config::ResponseHandler::on_response( + origin, + query_id, + querier.as_ref(), + response, + max_weight, + &self.context, + ); Ok(()) }, DescendOrigin(who) => self + .context .origin .as_mut() .ok_or(XcmError::BadOrigin)? .append_with(who) - .map_err(|_| XcmError::MultiLocationFull), + .map_err(|_| XcmError::LocationFull), ClearOrigin => { - self.origin = None; + self.context.origin = None; Ok(()) }, - ReportError { query_id, dest, max_response_weight: max_weight } => { + ReportError(response_info) => { // Report the given result by sending a QueryResponse XCM to a previously given outcome // destination if one was registered. - let response = Response::ExecutionResult(self.error); - let message = QueryResponse { query_id, response, max_weight }; - Config::XcmSender::send_xcm(dest, Xcm(vec![message]))?; + self.respond( + self.cloned_origin(), + Response::ExecutionResult(self.error), + response_info, + FeeReason::Report, + )?; Ok(()) }, - DepositAsset { assets, max_assets, beneficiary } => { - let deposited = self.holding.limited_saturating_take(assets, max_assets as usize); + DepositAsset { assets, beneficiary } => { + let deposited = self.holding.saturating_take(assets); for asset in deposited.into_assets_iter() { - Config::AssetTransactor::deposit_asset(&asset, &beneficiary)?; + Config::AssetTransactor::deposit_asset(&asset, &beneficiary, &self.context)?; } Ok(()) }, - DepositReserveAsset { assets, max_assets, dest, xcm } => { - let deposited = self.holding.limited_saturating_take(assets, max_assets as usize); + DepositReserveAsset { assets, dest, xcm } => { + let deposited = self.holding.saturating_take(assets); for asset in deposited.assets_iter() { - Config::AssetTransactor::deposit_asset(&asset, &dest)?; + Config::AssetTransactor::deposit_asset(&asset, &dest, &self.context)?; } // Note that we pass `None` as `maybe_failed_bin` and drop any assets which cannot // be reanchored because we have already called `deposit_asset` on all assets. let assets = Self::reanchored(deposited, &dest, None); let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; message.extend(xcm.0.into_iter()); - Config::XcmSender::send_xcm(dest, Xcm(message)).map_err(Into::into) + self.send(dest, Xcm(message), FeeReason::DepositReserveAsset)?; + Ok(()) }, InitiateReserveWithdraw { assets, reserve, xcm } => { // Note that here we are able to place any assets which could not be reanchored @@ -419,48 +640,58 @@ impl XcmExecutor { ); let mut message = vec![WithdrawAsset(assets), ClearOrigin]; message.extend(xcm.0.into_iter()); - Config::XcmSender::send_xcm(reserve, Xcm(message)).map_err(Into::into) + self.send(reserve, Xcm(message), FeeReason::InitiateReserveWithdraw)?; + Ok(()) }, InitiateTeleport { assets, dest, xcm } => { // We must do this first in order to resolve wildcards. let assets = self.holding.saturating_take(assets); for asset in assets.assets_iter() { - Config::AssetTransactor::check_out(&dest, &asset); + // We should check that the asset can actually be teleported out (for this to + // be in error, there would need to be an accounting violation by ourselves, + // so it's unlikely, but we don't want to allow that kind of bug to leak into + // a trusted chain. + Config::AssetTransactor::can_check_out(&dest, &asset, &self.context)?; + } + for asset in assets.assets_iter() { + Config::AssetTransactor::check_out(&dest, &asset, &self.context); } // Note that we pass `None` as `maybe_failed_bin` and drop any assets which cannot // be reanchored because we have already checked all assets out. let assets = Self::reanchored(assets, &dest, None); let mut message = vec![ReceiveTeleportedAsset(assets), ClearOrigin]; message.extend(xcm.0.into_iter()); - Config::XcmSender::send_xcm(dest, Xcm(message)).map_err(Into::into) + self.send(dest, Xcm(message), FeeReason::InitiateTeleport)?; + Ok(()) }, - QueryHolding { query_id, dest, assets, max_response_weight } => { + ReportHolding { response_info, assets } => { // Note that we pass `None` as `maybe_failed_bin` since no assets were ever removed // from Holding. - let assets = Self::reanchored(self.holding.min(&assets), &dest, None); - let max_weight = max_response_weight; - let response = Response::Assets(assets); - let instruction = QueryResponse { query_id, response, max_weight }; - Config::XcmSender::send_xcm(dest, Xcm(vec![instruction])).map_err(Into::into) + let assets = + Self::reanchored(self.holding.min(&assets), &response_info.destination, None); + self.respond( + self.cloned_origin(), + Response::Assets(assets), + response_info, + FeeReason::Report, + )?; + Ok(()) }, BuyExecution { fees, weight_limit } => { // There is no need to buy any weight is `weight_limit` is `Unlimited` since it // would indicate that `AllowTopLevelPaidExecutionFrom` was unused for execution // and thus there is some other reason why it has been determined that this XCM // should be executed. - if let Some(weight) = Option::::from(weight_limit) { + if let Some(weight) = Option::::from(weight_limit) { // pay for `weight` using up to `fees` of the holding register. let max_fee = self.holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; let unspent = self.trader.buy_weight(weight, max_fee)?; - self.holding.subsume_assets(unspent); + self.subsume_assets(unspent)?; } Ok(()) }, - RefundSurplus => { - self.refund_surplus(); - Ok(()) - }, + RefundSurplus => self.refund_surplus(), SetErrorHandler(mut handler) => { let handler_weight = Config::Weigher::weight(&mut handler) .map_err(|()| XcmError::WeightNotComputable)?; @@ -482,41 +713,283 @@ impl XcmExecutor { Ok(()) }, ClaimAsset { assets, ticket } => { - let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?; - let ok = Config::AssetClaims::claim_assets(origin, &ticket, &assets); + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + let ok = Config::AssetClaims::claim_assets(origin, &ticket, &assets, &self.context); ensure!(ok, XcmError::UnknownClaim); - for asset in assets.drain().into_iter() { - self.holding.subsume(asset); + for asset in assets.into_inner().into_iter() { + self.subsume_asset(asset)?; } Ok(()) }, Trap(code) => Err(XcmError::Trap(code)), SubscribeVersion { query_id, max_response_weight } => { - let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?.clone(); + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; // We don't allow derivative origins to subscribe since it would otherwise pose a // DoS risk. - ensure!(self.original_origin == origin, XcmError::BadOrigin); - Config::SubscriptionService::start(&origin, query_id, max_response_weight) + ensure!(&self.original_origin == origin, XcmError::BadOrigin); + Config::SubscriptionService::start( + origin, + query_id, + max_response_weight, + &self.context, + ) }, UnsubscribeVersion => { - let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?; + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; ensure!(&self.original_origin == origin, XcmError::BadOrigin); - Config::SubscriptionService::stop(origin) + Config::SubscriptionService::stop(origin, &self.context) + }, + BurnAsset(assets) => { + self.holding.saturating_take(assets.into()); + Ok(()) + }, + ExpectAsset(assets) => + self.holding.ensure_contains(&assets).map_err(|_| XcmError::ExpectationFalse), + ExpectOrigin(origin) => { + ensure!(self.context.origin == origin, XcmError::ExpectationFalse); + Ok(()) + }, + ExpectError(error) => { + ensure!(self.error == error, XcmError::ExpectationFalse); + Ok(()) + }, + QueryPallet { module_name, response_info } => { + let pallets = Config::PalletInstancesInfo::infos() + .into_iter() + .filter(|x| x.module_name.as_bytes() == &module_name[..]) + .map(|x| { + PalletInfo::new( + x.index as u32, + x.name.as_bytes().into(), + x.module_name.as_bytes().into(), + x.crate_version.major as u32, + x.crate_version.minor as u32, + x.crate_version.patch as u32, + ) + }) + .collect::, XcmError>>()?; + let QueryResponseInfo { destination, query_id, max_weight } = response_info; + let response = Response::PalletsInfo(pallets.try_into()?); + let querier = Self::to_querier(self.cloned_origin(), &destination)?; + let instruction = QueryResponse { query_id, response, max_weight, querier }; + let message = Xcm(vec![instruction]); + self.send(destination, message, FeeReason::QueryPallet)?; + Ok(()) + }, + ExpectPallet { index, name, module_name, crate_major, min_crate_minor } => { + let pallet = Config::PalletInstancesInfo::infos() + .into_iter() + .find(|x| x.index == index as usize) + .ok_or(XcmError::PalletNotFound)?; + ensure!(pallet.name.as_bytes() == &name[..], XcmError::NameMismatch); + ensure!(pallet.module_name.as_bytes() == &module_name[..], XcmError::NameMismatch); + let major = pallet.crate_version.major as u32; + ensure!(major == crate_major, XcmError::VersionIncompatible); + let minor = pallet.crate_version.minor as u32; + ensure!(minor >= min_crate_minor, XcmError::VersionIncompatible); + Ok(()) + }, + ReportTransactStatus(response_info) => { + self.respond( + self.cloned_origin(), + Response::DispatchResult(self.transact_status.clone()), + response_info, + FeeReason::Report, + )?; + Ok(()) + }, + ClearTransactStatus => { + self.transact_status = Default::default(); + Ok(()) + }, + UniversalOrigin(new_global) => { + let universal_location = Config::UniversalLocation::get(); + ensure!(universal_location.first() != Some(&new_global), XcmError::InvalidLocation); + let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + let origin_xform = (origin, new_global); + let ok = Config::UniversalAliases::contains(&origin_xform); + ensure!(ok, XcmError::InvalidLocation); + let (_, new_global) = origin_xform; + let new_origin = X1(new_global).relative_to(&universal_location); + self.context.origin = Some(new_origin); + Ok(()) + }, + ExportMessage { network, destination, xcm } => { + // The actual message send to the bridge for forwarding is prepended with `UniversalOrigin` + // and `DescendOrigin` in order to ensure that the message is executed with this Origin. + // + // Prepend the desired message with instructions which effectively rewrite the origin. + // + // This only works because the remote chain empowers the bridge + // to speak for the local network. + let origin = self.context.origin.ok_or(XcmError::BadOrigin)?; + let universal_source = Config::UniversalLocation::get() + .within_global(origin) + .map_err(|()| XcmError::Unanchored)?; + let hash = (self.origin_ref(), &destination).using_encoded(blake2_128); + let channel = u32::decode(&mut hash.as_ref()).unwrap_or(0); + // Hash identifies the lane on the exporter which we use. We use the pairwise + // combination of the origin and destination to ensure origin/destination pairs will + // generally have their own lanes. + let (ticket, fee) = validate_export::( + network, + channel, + universal_source, + destination, + xcm, + )?; + self.take_fee(fee, FeeReason::Export(network))?; + Config::MessageExporter::deliver(ticket)?; + Ok(()) + }, + LockAsset { asset, unlocker } => { + let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + let (remote_asset, context) = Self::try_reanchor(asset.clone(), &unlocker)?; + let lock_ticket = Config::AssetLocker::prepare_lock(unlocker, asset, origin)?; + let owner = + origin.reanchored(&unlocker, context).map_err(|_| XcmError::ReanchorFailed)?; + let msg = Xcm::<()>(vec![NoteUnlockable { asset: remote_asset, owner }]); + let (ticket, price) = validate_send::(unlocker, msg)?; + self.take_fee(price, FeeReason::LockAsset)?; + lock_ticket.enact()?; + Config::XcmSender::deliver(ticket)?; + Ok(()) + }, + UnlockAsset { asset, target } => { + let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + Config::AssetLocker::prepare_unlock(origin, asset, target)?.enact()?; + Ok(()) + }, + NoteUnlockable { asset, owner } => { + let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + Config::AssetLocker::note_unlockable(origin, asset, owner)?; + Ok(()) + }, + RequestUnlock { asset, locker } => { + let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + let remote_asset = Self::try_reanchor(asset.clone(), &locker)?.0; + let reduce_ticket = + Config::AssetLocker::prepare_reduce_unlockable(locker, asset, origin)?; + let msg = Xcm::<()>(vec![UnlockAsset { asset: remote_asset, target: origin }]); + let (ticket, price) = validate_send::(locker, msg)?; + self.take_fee(price, FeeReason::RequestUnlock)?; + reduce_ticket.enact()?; + Config::XcmSender::deliver(ticket)?; + Ok(()) + }, + ExchangeAsset { give, want, maximal } => { + let give = self.holding.saturating_take(give); + let r = + Config::AssetExchanger::exchange_asset(self.origin_ref(), give, &want, maximal); + let completed = r.is_ok(); + let received = r.unwrap_or_else(|a| a); + for asset in received.into_assets_iter() { + self.holding.subsume(asset); + } + if completed { + Ok(()) + } else { + Err(XcmError::NoDeal) + } + }, + SetFeesMode { jit_withdraw } => { + self.fees_mode = FeesMode { jit_withdraw }; + Ok(()) + }, + SetTopic(topic) => { + self.context.topic = Some(topic); + Ok(()) + }, + ClearTopic => { + self.context.topic = None; + Ok(()) + }, + AliasOrigin(_) => Err(XcmError::NoPermission), + UnpaidExecution { check_origin, .. } => { + ensure!( + check_origin.is_none() || self.context.origin == check_origin, + XcmError::BadOrigin + ); + Ok(()) }, - ExchangeAsset { .. } => Err(XcmError::Unimplemented), HrmpNewChannelOpenRequest { .. } => Err(XcmError::Unimplemented), HrmpChannelAccepted { .. } => Err(XcmError::Unimplemented), HrmpChannelClosing { .. } => Err(XcmError::Unimplemented), } } + fn take_fee(&mut self, fee: MultiAssets, reason: FeeReason) -> XcmResult { + if Config::FeeManager::is_waived(self.origin_ref(), reason) { + return Ok(()) + } + let paid = if self.fees_mode.jit_withdraw { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + for asset in fee.inner() { + Config::AssetTransactor::withdraw_asset(&asset, origin, Some(&self.context))?; + } + fee + } else { + self.holding.try_take(fee.into()).map_err(|_| XcmError::NotHoldingFees)?.into() + }; + Config::FeeManager::handle_fee(paid); + Ok(()) + } + + /// Calculates what `local_querier` would be from the perspective of `destination`. + fn to_querier( + local_querier: Option, + destination: &MultiLocation, + ) -> Result, XcmError> { + Ok(match local_querier { + None => None, + Some(q) => Some( + q.reanchored(&destination, Config::UniversalLocation::get()) + .map_err(|_| XcmError::ReanchorFailed)?, + ), + }) + } + + /// Send a bare `QueryResponse` message containing `response` informed by the given `info`. + /// + /// The `local_querier` argument is the querier (if any) specified from the *local* perspective. + fn respond( + &mut self, + local_querier: Option, + response: Response, + info: QueryResponseInfo, + fee_reason: FeeReason, + ) -> Result { + let querier = Self::to_querier(local_querier, &info.destination)?; + let QueryResponseInfo { destination, query_id, max_weight } = info; + let instruction = QueryResponse { query_id, response, max_weight, querier }; + let message = Xcm(vec![instruction]); + let (ticket, fee) = validate_send::(destination, message)?; + if !Config::FeeManager::is_waived(self.origin_ref(), fee_reason) { + let paid = self.holding.try_take(fee.into()).map_err(|_| XcmError::NotHoldingFees)?; + Config::FeeManager::handle_fee(paid.into()); + } + Config::XcmSender::deliver(ticket).map_err(Into::into) + } + + fn try_reanchor( + asset: MultiAsset, + destination: &MultiLocation, + ) -> Result<(MultiAsset, InteriorMultiLocation), XcmError> { + let reanchor_context = Config::UniversalLocation::get(); + let asset = asset + .reanchored(&destination, reanchor_context) + .map_err(|()| XcmError::ReanchorFailed)?; + Ok((asset, reanchor_context)) + } + /// NOTE: Any assets which were unable to be reanchored are introduced into `failed_bin`. fn reanchored( mut assets: Assets, dest: &MultiLocation, maybe_failed_bin: Option<&mut Assets>, ) -> MultiAssets { - assets.reanchor(dest, &Config::LocationInverter::ancestry(), maybe_failed_bin); + let reanchor_context = Config::UniversalLocation::get(); + assets.reanchor(dest, reanchor_context, maybe_failed_bin); assets.into_assets_iter().collect::>().into() } } diff --git a/xcm/xcm-executor/src/traits/asset_exchange.rs b/xcm/xcm-executor/src/traits/asset_exchange.rs new file mode 100644 index 000000000000..dd2eae23f8a2 --- /dev/null +++ b/xcm/xcm-executor/src/traits/asset_exchange.rs @@ -0,0 +1,58 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use crate::Assets; +use xcm::prelude::*; + +/// A service for exchanging assets. +pub trait AssetExchange { + /// Handler for exchanging an asset. + /// + /// - `origin`: The location attempting the exchange; this should generally not matter. + /// - `give`: The assets which have been removed from the caller. + /// - `want`: The minimum amount of assets which should be given to the caller in case any + /// exchange happens. If more assets are provided, then they should generally be of the + /// same asset class if at all possible. + /// - `maximal`: If `true`, then as much as possible should be exchanged. + /// + /// `Ok` is returned along with the new set of assets which have been exchanged for `give`. At + /// least want must be in the set. Some assets originally in `give` may also be in this set. In + /// the case of returning an `Err`, then `give` is returned. + fn exchange_asset( + origin: Option<&MultiLocation>, + give: Assets, + want: &MultiAssets, + maximal: bool, + ) -> Result; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl AssetExchange for Tuple { + fn exchange_asset( + origin: Option<&MultiLocation>, + give: Assets, + want: &MultiAssets, + maximal: bool, + ) -> Result { + for_tuples!( #( + let give = match Tuple::exchange_asset(origin, give, want, maximal) { + Ok(r) => return Ok(r), + Err(a) => a, + }; + )* ); + Err(give) + } +} diff --git a/xcm/xcm-executor/src/traits/asset_lock.rs b/xcm/xcm-executor/src/traits/asset_lock.rs new file mode 100644 index 000000000000..8d030ed1d5c1 --- /dev/null +++ b/xcm/xcm-executor/src/traits/asset_lock.rs @@ -0,0 +1,152 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use sp_std::convert::Infallible; +use xcm::prelude::*; + +#[derive(Debug)] +pub enum LockError { + NotApplicable, + WouldClobber, + BadOrigin, + NotLocked, + NotEnoughLocked, + Unimplemented, + NotTrusted, + BadOwner, + UnknownAsset, + AssetNotOwned, + NoResources, + UnexpectedState, + InUse, +} + +impl From for XcmError { + fn from(e: LockError) -> XcmError { + use LockError::*; + match e { + NotApplicable => XcmError::AssetNotFound, + BadOrigin => XcmError::BadOrigin, + WouldClobber | NotLocked | NotEnoughLocked | Unimplemented | NotTrusted | + BadOwner | UnknownAsset | AssetNotOwned | NoResources | UnexpectedState | InUse => + XcmError::LockError, + } + } +} + +pub trait Enact { + /// Enact a lock. This should generally be infallible if called immediately after being + /// received. + fn enact(self) -> Result<(), LockError>; +} + +impl Enact for Infallible { + fn enact(self) -> Result<(), LockError> { + unreachable!() + } +} + +/// Define a handler for notification of an asset being locked and for the unlock instruction. +pub trait AssetLock { + /// `Enact` implementer for `prepare_lock`. This type may be dropped safely to avoid doing the + /// lock. + type LockTicket: Enact; + + /// `Enact` implementer for `prepare_unlock`. This type may be dropped safely to avoid doing the + /// unlock. + type UnlockTicket: Enact; + + /// `Enact` implementer for `prepare_reduce_unlockable`. This type may be dropped safely to avoid doing the + /// unlock. + type ReduceTicket: Enact; + + /// Prepare to lock an asset. On success, a `Self::LockTicket` it returned, which can be used + /// to actually enact the lock. + /// + /// WARNING: Don't call this with an undropped instance of `Self::LockTicket` or + /// `Self::UnlockTicket`. + fn prepare_lock( + unlocker: MultiLocation, + asset: MultiAsset, + owner: MultiLocation, + ) -> Result; + + /// Prepare to unlock an asset. On success, a `Self::UnlockTicket` it returned, which can be + /// used to actually enact the lock. + /// + /// WARNING: Don't call this with an undropped instance of `Self::LockTicket` or + /// `Self::UnlockTicket`. + fn prepare_unlock( + locker: MultiLocation, + asset: MultiAsset, + owner: MultiLocation, + ) -> Result; + + /// Handler for when a location reports to us that an asset has been locked for us to unlock + /// at a later stage. + /// + /// If there is no way to handle the lock report, then this should return an error so that the + /// sending chain can ensure the lock does not remain. + /// + /// We should only act upon this message if we believe that the `origin` is honest. + fn note_unlockable( + locker: MultiLocation, + asset: MultiAsset, + owner: MultiLocation, + ) -> Result<(), LockError>; + + /// Handler for when an owner wishes to unlock an asset on a remote chain. + /// + /// Returns a ticket which can be used to actually note the reduction in unlockable assets that + /// `owner` commands on `locker`. + /// + /// WARNING: Don't call this with an undropped instance of `Self::ReduceTicket`. + fn prepare_reduce_unlockable( + locker: MultiLocation, + asset: MultiAsset, + owner: MultiLocation, + ) -> Result; +} + +impl AssetLock for () { + type LockTicket = Infallible; + type UnlockTicket = Infallible; + type ReduceTicket = Infallible; + fn prepare_lock( + _: MultiLocation, + _: MultiAsset, + _: MultiLocation, + ) -> Result { + Err(LockError::NotApplicable) + } + fn prepare_unlock( + _: MultiLocation, + _: MultiAsset, + _: MultiLocation, + ) -> Result { + Err(LockError::NotApplicable) + } + fn note_unlockable(_: MultiLocation, _: MultiAsset, _: MultiLocation) -> Result<(), LockError> { + Err(LockError::NotApplicable) + } + fn prepare_reduce_unlockable( + _: MultiLocation, + _: MultiAsset, + _: MultiLocation, + ) -> Result { + Err(LockError::NotApplicable) + } +} diff --git a/xcm/xcm-executor/src/traits/conversion.rs b/xcm/xcm-executor/src/traits/conversion.rs index 8ea91b598e63..73b02bfe0adc 100644 --- a/xcm/xcm-executor/src/traits/conversion.rs +++ b/xcm/xcm-executor/src/traits/conversion.rs @@ -14,9 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . +use frame_support::traits::{Contains, OriginTrait}; use parity_scale_codec::{Decode, Encode}; -use sp_std::{borrow::Borrow, prelude::*, result::Result}; -use xcm::latest::{MultiLocation, OriginKind}; +use sp_runtime::{traits::Dispatchable, DispatchErrorWithPostInfo}; +use sp_std::{borrow::Borrow, marker::PhantomData, prelude::*, result::Result}; +use xcm::latest::prelude::*; /// Generic third-party conversion trait. Use this when you don't want to force the user to use default /// implementations of `From` and `Into` for the types you wish to convert between. @@ -204,9 +206,43 @@ impl ConvertOrigin for Tuple { } } -/// Means of inverting a location: given a location which describes a `target` interpreted from the -/// `source`, this will provide the corresponding location which describes the `source`. -pub trait InvertLocation { - fn ancestry() -> MultiLocation; - fn invert_location(l: &MultiLocation) -> Result; +/// Defines how a call is dispatched with given origin. +/// Allows to customize call dispatch, such as adapting the origin based on the call +/// or modifying the call. +pub trait CallDispatcher { + fn dispatch( + call: Call, + origin: Call::RuntimeOrigin, + ) -> Result>; +} + +pub struct WithOriginFilter(PhantomData); +impl CallDispatcher for WithOriginFilter +where + Call: Dispatchable, + Call::RuntimeOrigin: OriginTrait, + <::RuntimeOrigin as OriginTrait>::Call: 'static, + Filter: Contains<<::RuntimeOrigin as OriginTrait>::Call> + 'static, +{ + fn dispatch( + call: Call, + mut origin: ::RuntimeOrigin, + ) -> Result< + ::PostInfo, + DispatchErrorWithPostInfo<::PostInfo>, + > { + origin.add_filter(Filter::contains); + call.dispatch(origin) + } +} + +// We implement it for every calls so they can dispatch themselves +// (without any change). +impl CallDispatcher for Call { + fn dispatch( + call: Call, + origin: Call::RuntimeOrigin, + ) -> Result> { + call.dispatch(origin) + } } diff --git a/xcm/xcm-executor/src/traits/drop_assets.rs b/xcm/xcm-executor/src/traits/drop_assets.rs index db61dd23e17b..1119f585345f 100644 --- a/xcm/xcm-executor/src/traits/drop_assets.rs +++ b/xcm/xcm-executor/src/traits/drop_assets.rs @@ -17,16 +17,15 @@ use crate::Assets; use core::marker::PhantomData; use frame_support::traits::Contains; -use sp_runtime::traits::Zero; -use xcm::latest::{MultiAssets, MultiLocation, Weight}; +use xcm::latest::{MultiAssets, MultiLocation, Weight, XcmContext}; /// Define a handler for when some non-empty `Assets` value should be dropped. pub trait DropAssets { /// Handler for receiving dropped assets. Returns the weight consumed by this operation. - fn drop_assets(origin: &MultiLocation, assets: Assets) -> Weight; + fn drop_assets(origin: &MultiLocation, assets: Assets, context: &XcmContext) -> Weight; } impl DropAssets for () { - fn drop_assets(_origin: &MultiLocation, _assets: Assets) -> Weight { + fn drop_assets(_origin: &MultiLocation, _assets: Assets, _context: &XcmContext) -> Weight { Weight::zero() } } @@ -36,9 +35,9 @@ impl DropAssets for () { pub struct FilterAssets(PhantomData<(D, A)>); impl> DropAssets for FilterAssets { - fn drop_assets(origin: &MultiLocation, assets: Assets) -> Weight { + fn drop_assets(origin: &MultiLocation, assets: Assets, context: &XcmContext) -> Weight { if A::contains(&assets) { - D::drop_assets(origin, assets) + D::drop_assets(origin, assets, context) } else { Weight::zero() } @@ -51,9 +50,9 @@ impl> DropAssets for FilterAssets { pub struct FilterOrigin(PhantomData<(D, O)>); impl> DropAssets for FilterOrigin { - fn drop_assets(origin: &MultiLocation, assets: Assets) -> Weight { + fn drop_assets(origin: &MultiLocation, assets: Assets, context: &XcmContext) -> Weight { if O::contains(origin) { - D::drop_assets(origin, assets) + D::drop_assets(origin, assets, context) } else { Weight::zero() } @@ -64,14 +63,24 @@ impl> DropAssets for FilterOrigin bool; + fn claim_assets( + origin: &MultiLocation, + ticket: &MultiLocation, + what: &MultiAssets, + context: &XcmContext, + ) -> bool; } #[impl_trait_for_tuples::impl_for_tuples(30)] impl ClaimAssets for Tuple { - fn claim_assets(origin: &MultiLocation, ticket: &MultiLocation, what: &MultiAssets) -> bool { + fn claim_assets( + origin: &MultiLocation, + ticket: &MultiLocation, + what: &MultiAssets, + context: &XcmContext, + ) -> bool { for_tuples!( #( - if Tuple::claim_assets(origin, ticket, what) { + if Tuple::claim_assets(origin, ticket, what, context) { return true; } )* ); diff --git a/xcm/xcm-executor/src/traits/export.rs b/xcm/xcm-executor/src/traits/export.rs new file mode 100644 index 000000000000..61b76addfe4c --- /dev/null +++ b/xcm/xcm-executor/src/traits/export.rs @@ -0,0 +1,146 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use xcm::latest::prelude::*; + +/// Utility for delivering a message to a system under a different (non-local) consensus with a +/// spoofed origin. This essentially defines the behaviour of the `ExportMessage` XCM instruction. +/// +/// This is quite different to `SendXcm`; `SendXcm` assumes that the local side's location will be +/// preserved to be represented as the value of the Origin register in the messages execution. +/// +/// This trait on the other hand assumes that we do not necessarily want the Origin register to +/// contain the local (i.e. the caller chain's) location, since it will generally be exporting a +/// message on behalf of another consensus system. Therefore in addition to the message, the +/// destination must be given in two parts: the network and the interior location within it. +/// +/// We also require the caller to state exactly what location they purport to be representing. The +/// destination must accept the local location to represent that location or the operation will +/// fail. +pub trait ExportXcm { + /// Intermediate value which connects the two phaases of the export operation. + type Ticket; + + /// Check whether the given `message` is deliverable to the given `destination` on `network`, + /// spoofing its source as `universal_source` and if so determine the cost which will be paid by + /// this chain to do so, returning a `Ticket` token which can be used to enact delivery. + /// + /// The `channel` to be used on the `network`'s export mechanism (bridge, probably) must also + /// be provided. + /// + /// The `destination` and `message` must be `Some` (or else an error will be returned) and they + /// may only be consumed if the `Err` is not `NotApplicable`. + /// + /// If it is not a destination which can be reached with this type but possibly could by others, + /// then this *MUST* return `NotApplicable`. Any other error will cause the tuple + /// implementation (used to compose routing systems from different delivery agents) to exit + /// early without trying alternative means of delivery. + fn validate( + network: NetworkId, + channel: u32, + universal_source: &mut Option, + destination: &mut Option, + message: &mut Option>, + ) -> SendResult; + + /// Actually carry out the delivery operation for a previously validated message sending. + /// + /// The implementation should do everything possible to ensure that this function is infallible + /// if called immediately after `validate`. Returning an error here would result in a price + /// paid without the service being delivered. + fn deliver(ticket: Self::Ticket) -> Result; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl ExportXcm for Tuple { + for_tuples! { type Ticket = (#( Option ),* ); } + + fn validate( + network: NetworkId, + channel: u32, + universal_source: &mut Option, + destination: &mut Option, + message: &mut Option>, + ) -> SendResult { + let mut maybe_cost: Option = None; + let one_ticket: Self::Ticket = (for_tuples! { #( + if maybe_cost.is_some() { + None + } else { + match Tuple::validate(network, channel, universal_source, destination, message) { + Err(SendError::NotApplicable) => None, + Err(e) => { return Err(e) }, + Ok((v, c)) => { + maybe_cost = Some(c); + Some(v) + }, + } + } + ),* }); + if let Some(cost) = maybe_cost { + Ok((one_ticket, cost)) + } else { + Err(SendError::NotApplicable) + } + } + + fn deliver(one_ticket: Self::Ticket) -> Result { + for_tuples!( #( + if let Some(validated) = one_ticket.Tuple.take() { + return Tuple::deliver(validated); + } + )* ); + Err(SendError::Unroutable) + } +} + +/// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps +/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +pub fn validate_export( + network: NetworkId, + channel: u32, + universal_source: InteriorMultiLocation, + dest: InteriorMultiLocation, + msg: Xcm<()>, +) -> SendResult { + T::validate(network, channel, &mut Some(universal_source), &mut Some(dest), &mut Some(msg)) +} + +/// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps +/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// +/// Returns either `Ok` with the price of the delivery, or `Err` with the reason why the message +/// could not be sent. +/// +/// Generally you'll want to validate and get the price first to ensure that the sender can pay it +/// before actually doing the delivery. +pub fn export_xcm( + network: NetworkId, + channel: u32, + universal_source: InteriorMultiLocation, + dest: InteriorMultiLocation, + msg: Xcm<()>, +) -> Result<(XcmHash, MultiAssets), SendError> { + let (ticket, price) = T::validate( + network, + channel, + &mut Some(universal_source), + &mut Some(dest), + &mut Some(msg), + )?; + let hash = T::deliver(ticket)?; + Ok((hash, price)) +} diff --git a/xcm/xcm-executor/src/traits/fee_manager.rs b/xcm/xcm-executor/src/traits/fee_manager.rs new file mode 100644 index 000000000000..b883a6d573c8 --- /dev/null +++ b/xcm/xcm-executor/src/traits/fee_manager.rs @@ -0,0 +1,59 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use xcm::prelude::*; + +/// Handle stuff to do with taking fees in certain XCM instructions. +pub trait FeeManager { + /// Determine if a fee which would normally payable should be waived. + fn is_waived(origin: Option<&MultiLocation>, r: FeeReason) -> bool; + + /// Do something with the fee which has been paid. Doing nothing here silently burns the + /// fees. + fn handle_fee(fee: MultiAssets); +} + +/// Context under which a fee is paid. +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum FeeReason { + /// When a reporting instruction is called. + Report, + /// When the `TransferReserveAsset` instruction is called. + TransferReserveAsset, + /// When the `DepositReserveAsset` instruction is called. + DepositReserveAsset, + /// When the `InitiateReserveWithdraw` instruction is called. + InitiateReserveWithdraw, + /// When the `InitiateTeleport` instruction is called. + InitiateTeleport, + /// When the `QueryPallet` instruction is called. + QueryPallet, + /// When the `ExportMessage` instruction is called (and includes the network ID). + Export(NetworkId), + /// The `charge_fees` API. + ChargeFees, + /// When the `LockAsset` instruction is called. + LockAsset, + /// When the `RequestUnlock` instruction is called. + RequestUnlock, +} + +impl FeeManager for () { + fn is_waived(_: Option<&MultiLocation>, _: FeeReason) -> bool { + true + } + fn handle_fee(_: MultiAssets) {} +} diff --git a/xcm/xcm-executor/src/traits/filter_asset_location.rs b/xcm/xcm-executor/src/traits/filter_asset_location.rs index 31b9c47a828c..ec9fecbab640 100644 --- a/xcm/xcm-executor/src/traits/filter_asset_location.rs +++ b/xcm/xcm-executor/src/traits/filter_asset_location.rs @@ -14,28 +14,21 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . +use frame_support::traits::ContainsPair; use xcm::latest::{MultiAsset, MultiLocation}; /// Filters assets/location pairs. /// /// Can be amalgamated into tuples. If any item returns `true`, it short-circuits, else `false` is returned. +#[deprecated = "Use `frame_support::traits::ContainsPair` instead"] pub trait FilterAssetLocation { /// A filter to distinguish between asset/location pairs. - fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool; + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool; } -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl FilterAssetLocation for Tuple { - fn filter_asset_location(what: &MultiAsset, origin: &MultiLocation) -> bool { - for_tuples!( #( - if Tuple::filter_asset_location(what, origin) { return true } - )* ); - log::trace!( - target: "xcm::filter_asset_location", - "got filtered: what: {:?}, origin: {:?}", - what, - origin, - ); - false +#[allow(deprecated)] +impl> FilterAssetLocation for T { + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + T::contains(asset, origin) } } diff --git a/xcm/xcm-executor/src/traits/matches_fungible.rs b/xcm/xcm-executor/src/traits/matches_fungible.rs deleted file mode 100644 index 4989f263a63d..000000000000 --- a/xcm/xcm-executor/src/traits/matches_fungible.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot 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. - -// Polkadot 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 Polkadot. If not, see . - -use xcm::latest::MultiAsset; - -pub trait MatchesFungible { - fn matches_fungible(a: &MultiAsset) -> Option; -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl MatchesFungible for Tuple { - fn matches_fungible(a: &MultiAsset) -> Option { - for_tuples!( #( - match Tuple::matches_fungible(a) { o @ Some(_) => return o, _ => () } - )* ); - log::trace!(target: "xcm::matches_fungible", "did not match fungible asset: {:?}", &a); - None - } -} diff --git a/xcm/xcm-executor/src/traits/mod.rs b/xcm/xcm-executor/src/traits/mod.rs index 1312771e719b..94fe7e98a7db 100644 --- a/xcm/xcm-executor/src/traits/mod.rs +++ b/xcm/xcm-executor/src/traits/mod.rs @@ -17,15 +17,26 @@ //! Various traits used in configuring the executor. mod conversion; -pub use conversion::{Convert, ConvertOrigin, Decoded, Encoded, Identity, InvertLocation, JustTry}; +pub use conversion::{ + CallDispatcher, Convert, ConvertOrigin, Decoded, Encoded, Identity, JustTry, WithOriginFilter, +}; mod drop_assets; pub use drop_assets::{ClaimAssets, DropAssets}; +mod asset_lock; +pub use asset_lock::{AssetLock, Enact, LockError}; +mod asset_exchange; +pub use asset_exchange::AssetExchange; +mod export; +pub use export::{export_xcm, validate_export, ExportXcm}; +mod fee_manager; +pub use fee_manager::{FeeManager, FeeReason}; mod filter_asset_location; +#[allow(deprecated)] pub use filter_asset_location::FilterAssetLocation; -mod matches_fungible; -pub use matches_fungible::MatchesFungible; -mod matches_fungibles; -pub use matches_fungibles::{Error, MatchesFungibles}; +mod token_matching; +pub use token_matching::{ + Error, MatchesFungible, MatchesFungibles, MatchesNonFungible, MatchesNonFungibles, +}; mod on_response; pub use on_response::{OnResponse, VersionChangeNotifier}; mod should_execute; @@ -34,3 +45,13 @@ mod transact_asset; pub use transact_asset::TransactAsset; mod weight; pub use weight::{WeightBounds, WeightTrader}; + +pub mod prelude { + pub use super::{ + export_xcm, validate_export, AssetExchange, AssetLock, ClaimAssets, Convert, ConvertOrigin, + Decoded, DropAssets, Enact, Encoded, Error, ExportXcm, FeeManager, FeeReason, Identity, + JustTry, LockError, MatchesFungible, MatchesFungibles, MatchesNonFungible, + MatchesNonFungibles, OnResponse, ShouldExecute, TransactAsset, VersionChangeNotifier, + WeightBounds, WeightTrader, WithOriginFilter, + }; +} diff --git a/xcm/xcm-executor/src/traits/on_response.rs b/xcm/xcm-executor/src/traits/on_response.rs index 0da0a1cb6087..dbde3f940f41 100644 --- a/xcm/xcm-executor/src/traits/on_response.rs +++ b/xcm/xcm-executor/src/traits/on_response.rs @@ -14,32 +14,45 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use sp_runtime::traits::Zero; use xcm::latest::{ - Error as XcmError, MultiLocation, QueryId, Response, Result as XcmResult, Weight, + Error as XcmError, MultiLocation, QueryId, Response, Result as XcmResult, Weight, XcmContext, }; /// Define what needs to be done upon receiving a query response. pub trait OnResponse { - /// Returns `true` if we are expecting a response from `origin` for query `query_id`. - fn expecting_response(origin: &MultiLocation, query_id: u64) -> bool; - /// Handler for receiving a `response` from `origin` relating to `query_id`. + /// Returns `true` if we are expecting a response from `origin` for query `query_id` that was + /// queried by `querier`. + fn expecting_response( + origin: &MultiLocation, + query_id: u64, + querier: Option<&MultiLocation>, + ) -> bool; + /// Handler for receiving a `response` from `origin` relating to `query_id` initiated by + /// `querier`. fn on_response( origin: &MultiLocation, query_id: u64, + querier: Option<&MultiLocation>, response: Response, max_weight: Weight, + context: &XcmContext, ) -> Weight; } impl OnResponse for () { - fn expecting_response(_origin: &MultiLocation, _query_id: u64) -> bool { + fn expecting_response( + _origin: &MultiLocation, + _query_id: u64, + _querier: Option<&MultiLocation>, + ) -> bool { false } fn on_response( _origin: &MultiLocation, _query_id: u64, + _querier: Option<&MultiLocation>, _response: Response, _max_weight: Weight, + _context: &XcmContext, ) -> Weight { Weight::zero() } @@ -50,26 +63,31 @@ pub trait VersionChangeNotifier { /// Start notifying `location` should the XCM version of this chain change. /// /// When it does, this type should ensure a `QueryResponse` message is sent with the given - /// `query_id` & `max_weight` and with a `response` of `Repsonse::Version`. This should happen + /// `query_id` & `max_weight` and with a `response` of `Response::Version`. This should happen /// until/unless `stop` is called with the correct `query_id`. /// /// If the `location` has an ongoing notification and when this function is called, then an /// error should be returned. - fn start(location: &MultiLocation, query_id: QueryId, max_weight: u64) -> XcmResult; + fn start( + location: &MultiLocation, + query_id: QueryId, + max_weight: Weight, + context: &XcmContext, + ) -> XcmResult; /// Stop notifying `location` should the XCM change. Returns an error if there is no existing /// notification set up. - fn stop(location: &MultiLocation) -> XcmResult; + fn stop(location: &MultiLocation, context: &XcmContext) -> XcmResult; /// Return true if a location is subscribed to XCM version changes. fn is_subscribed(location: &MultiLocation) -> bool; } impl VersionChangeNotifier for () { - fn start(_: &MultiLocation, _: QueryId, _: u64) -> XcmResult { + fn start(_: &MultiLocation, _: QueryId, _: Weight, _: &XcmContext) -> XcmResult { Err(XcmError::Unimplemented) } - fn stop(_: &MultiLocation) -> XcmResult { + fn stop(_: &MultiLocation, _: &XcmContext) -> XcmResult { Err(XcmError::Unimplemented) } fn is_subscribed(_: &MultiLocation) -> bool { diff --git a/xcm/xcm-executor/src/traits/should_execute.rs b/xcm/xcm-executor/src/traits/should_execute.rs index ca3283467cfb..7db8fbe4a09e 100644 --- a/xcm/xcm-executor/src/traits/should_execute.rs +++ b/xcm/xcm-executor/src/traits/should_execute.rs @@ -15,7 +15,7 @@ // along with Polkadot. If not, see . use sp_std::result::Result; -use xcm::latest::{MultiLocation, Weight, Xcm}; +use xcm::latest::{Instruction, MultiLocation, Weight}; /// Trait to determine whether the execution engine should actually execute a given XCM. /// @@ -25,14 +25,14 @@ pub trait ShouldExecute { /// Returns `true` if the given `message` may be executed. /// /// - `origin`: The origin (sender) of the message. - /// - `message`: The message itself. + /// - `instructions`: The message itself. /// - `max_weight`: The (possibly over-) estimation of the weight of execution of the message. /// - `weight_credit`: The pre-established amount of weight that the system has determined this /// message may utilize in its execution. Typically non-zero only because of prior fee /// payment, but could in principle be due to other factors. fn should_execute( origin: &MultiLocation, - message: &mut Xcm, + instructions: &mut [Instruction], max_weight: Weight, weight_credit: &mut Weight, ) -> Result<(), ()>; @@ -42,21 +42,21 @@ pub trait ShouldExecute { impl ShouldExecute for Tuple { fn should_execute( origin: &MultiLocation, - message: &mut Xcm, + instructions: &mut [Instruction], max_weight: Weight, weight_credit: &mut Weight, ) -> Result<(), ()> { for_tuples!( #( - match Tuple::should_execute(origin, message, max_weight, weight_credit) { + match Tuple::should_execute(origin, instructions, max_weight, weight_credit) { Ok(()) => return Ok(()), _ => (), } )* ); log::trace!( target: "xcm::should_execute", - "did not pass barrier: origin: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}", + "did not pass barrier: origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}", origin, - message, + instructions, max_weight, weight_credit, ); diff --git a/xcm/xcm-executor/src/traits/matches_fungibles.rs b/xcm/xcm-executor/src/traits/token_matching.rs similarity index 51% rename from xcm/xcm-executor/src/traits/matches_fungibles.rs rename to xcm/xcm-executor/src/traits/token_matching.rs index f5baafdcd97a..befff6b1b726 100644 --- a/xcm/xcm-executor/src/traits/matches_fungibles.rs +++ b/xcm/xcm-executor/src/traits/token_matching.rs @@ -15,7 +15,37 @@ // along with Polkadot. If not, see . use sp_std::result; -use xcm::latest::{Error as XcmError, MultiAsset}; +use xcm::latest::prelude::*; + +pub trait MatchesFungible { + fn matches_fungible(a: &MultiAsset) -> Option; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl MatchesFungible for Tuple { + fn matches_fungible(a: &MultiAsset) -> Option { + for_tuples!( #( + match Tuple::matches_fungible(a) { o @ Some(_) => return o, _ => () } + )* ); + log::trace!(target: "xcm::matches_fungible", "did not match fungible asset: {:?}", &a); + None + } +} + +pub trait MatchesNonFungible { + fn matches_nonfungible(a: &MultiAsset) -> Option; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl MatchesNonFungible for Tuple { + fn matches_nonfungible(a: &MultiAsset) -> Option { + for_tuples!( #( + match Tuple::matches_nonfungible(a) { o @ Some(_) => return o, _ => () } + )* ); + log::trace!(target: "xcm::matches_non_fungible", "did not match non-fungible asset: {:?}", &a); + None + } +} /// Errors associated with [`MatchesFungibles`] operation. pub enum Error { @@ -25,8 +55,10 @@ pub enum Error { AccountIdConversionFailed, /// `u128` amount to currency `Balance` conversion failed. AmountToBalanceConversionFailed, - /// `MultiLocation` to `AssetId` conversion failed. + /// `MultiLocation` to `AssetId`/`ClassId` conversion failed. AssetIdConversionFailed, + /// `AssetInstance` to non-fungibles instance ID conversion failed. + InstanceConversionFailed, } impl From for XcmError { @@ -38,6 +70,7 @@ impl From for XcmError { Error::AmountToBalanceConversionFailed => FailedToTransactAsset("AmountToBalanceConversionFailed"), Error::AssetIdConversionFailed => FailedToTransactAsset("AssetIdConversionFailed"), + Error::InstanceConversionFailed => FailedToTransactAsset("InstanceConversionFailed"), } } } @@ -47,7 +80,7 @@ pub trait MatchesFungibles { } #[impl_trait_for_tuples::impl_for_tuples(30)] -impl MatchesFungibles for Tuple { +impl MatchesFungibles for Tuple { fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), Error> { for_tuples!( #( match Tuple::matches_fungibles(a) { o @ Ok(_) => return o, _ => () } @@ -56,3 +89,18 @@ impl MatchesFungibles for Tupl Err(Error::AssetNotFound) } } + +pub trait MatchesNonFungibles { + fn matches_nonfungibles(a: &MultiAsset) -> result::Result<(AssetId, Instance), Error>; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl MatchesNonFungibles for Tuple { + fn matches_nonfungibles(a: &MultiAsset) -> result::Result<(AssetId, Instance), Error> { + for_tuples!( #( + match Tuple::matches_nonfungibles(a) { o @ Ok(_) => return o, _ => () } + )* ); + log::trace!(target: "xcm::matches_non_fungibles", "did not match fungibles asset: {:?}", &a); + Err(Error::AssetNotFound) + } +} diff --git a/xcm/xcm-executor/src/traits/transact_asset.rs b/xcm/xcm-executor/src/traits/transact_asset.rs index 5b8c37bb59a0..9f4b9b5ad392 100644 --- a/xcm/xcm-executor/src/traits/transact_asset.rs +++ b/xcm/xcm-executor/src/traits/transact_asset.rs @@ -16,7 +16,7 @@ use crate::Assets; use sp_std::result::Result; -use xcm::latest::{Error as XcmError, MultiAsset, MultiLocation, Result as XcmResult}; +use xcm::latest::{Error as XcmError, MultiAsset, MultiLocation, Result as XcmResult, XcmContext}; /// Facility for asset transacting. /// @@ -26,10 +26,14 @@ use xcm::latest::{Error as XcmError, MultiAsset, MultiLocation, Result as XcmRes /// Can be amalgamated as a tuple of items that implement this trait. In such executions, if any of the transactors /// returns `Ok(())`, then it will short circuit. Else, execution is passed to the next transactor. pub trait TransactAsset { - /// Ensure that `check_in` will result in `Ok`. + /// Ensure that `check_in` will do as expected. /// /// When composed as a tuple, all type-items are called and at least one must result in `Ok`. - fn can_check_in(_origin: &MultiLocation, _what: &MultiAsset) -> XcmResult { + fn can_check_in( + _origin: &MultiLocation, + _what: &MultiAsset, + _context: &XcmContext, + ) -> XcmResult { Err(XcmError::Unimplemented) } @@ -46,7 +50,18 @@ pub trait TransactAsset { /// /// When composed as a tuple, all type-items are called. It is up to the implementer that there exists no /// value for `_what` which can cause side-effects for more than one of the type-items. - fn check_in(_origin: &MultiLocation, _what: &MultiAsset) {} + fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {} + + /// Ensure that `check_out` will do as expected. + /// + /// When composed as a tuple, all type-items are called and at least one must result in `Ok`. + fn can_check_out( + _dest: &MultiLocation, + _what: &MultiAsset, + _context: &XcmContext, + ) -> XcmResult { + Err(XcmError::Unimplemented) + } /// An asset has been teleported out to the given destination. This should do whatever housekeeping is needed. /// @@ -58,20 +73,28 @@ pub trait TransactAsset { /// /// When composed as a tuple, all type-items are called. It is up to the implementer that there exists no /// value for `_what` which can cause side-effects for more than one of the type-items. - fn check_out(_dest: &MultiLocation, _what: &MultiAsset) {} + fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {} /// Deposit the `what` asset into the account of `who`. /// /// Implementations should return `XcmError::FailedToTransactAsset` if deposit failed. - fn deposit_asset(_what: &MultiAsset, _who: &MultiLocation) -> XcmResult { + fn deposit_asset(_what: &MultiAsset, _who: &MultiLocation, _context: &XcmContext) -> XcmResult { Err(XcmError::Unimplemented) } /// Withdraw the given asset from the consensus system. Return the actual asset(s) withdrawn, /// which should always be equal to `_what`. /// + /// The XCM `_maybe_context` parameter may be `None` when the caller of `withdraw_asset` is + /// outside of the context of a currently-executing XCM. An example will be the `charge_fees` + /// method in the XCM executor. + /// /// Implementations should return `XcmError::FailedToTransactAsset` if withdraw failed. - fn withdraw_asset(_what: &MultiAsset, _who: &MultiLocation) -> Result { + fn withdraw_asset( + _what: &MultiAsset, + _who: &MultiLocation, + _maybe_context: Option<&XcmContext>, + ) -> Result { Err(XcmError::Unimplemented) } @@ -88,6 +111,7 @@ pub trait TransactAsset { _asset: &MultiAsset, _from: &MultiLocation, _to: &MultiLocation, + _context: &XcmContext, ) -> Result { Err(XcmError::Unimplemented) } @@ -100,12 +124,13 @@ pub trait TransactAsset { asset: &MultiAsset, from: &MultiLocation, to: &MultiLocation, + context: &XcmContext, ) -> Result { - match Self::internal_transfer_asset(asset, from, to) { - Err(XcmError::Unimplemented) => { - let assets = Self::withdraw_asset(asset, from)?; + match Self::internal_transfer_asset(asset, from, to, context) { + Err(XcmError::AssetNotFound | XcmError::Unimplemented) => { + let assets = Self::withdraw_asset(asset, from, Some(context))?; // Not a very forgiving attitude; once we implement roll-backs then it'll be nicer. - Self::deposit_asset(asset, to)?; + Self::deposit_asset(asset, to, context)?; Ok(assets) }, result => result, @@ -115,62 +140,86 @@ pub trait TransactAsset { #[impl_trait_for_tuples::impl_for_tuples(30)] impl TransactAsset for Tuple { - fn can_check_in(origin: &MultiLocation, what: &MultiAsset) -> XcmResult { + fn can_check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { for_tuples!( #( - match Tuple::can_check_in(origin, what) { + match Tuple::can_check_in(origin, what, context) { Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), r => return r, } )* ); log::trace!( target: "xcm::TransactAsset::can_check_in", - "asset not found: what: {:?}, origin: {:?}", + "asset not found: what: {:?}, origin: {:?}, context: {:?}", what, origin, + context, ); Err(XcmError::AssetNotFound) } - fn check_in(origin: &MultiLocation, what: &MultiAsset) { + fn check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) { for_tuples!( #( - Tuple::check_in(origin, what); + Tuple::check_in(origin, what, context); )* ); } - fn check_out(dest: &MultiLocation, what: &MultiAsset) { + fn can_check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { for_tuples!( #( - Tuple::check_out(dest, what); + match Tuple::can_check_out(dest, what, context) { + Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), + r => return r, + } + )* ); + log::trace!( + target: "xcm::TransactAsset::can_check_out", + "asset not found: what: {:?}, dest: {:?}, context: {:?}", + what, + dest, + context, + ); + Err(XcmError::AssetNotFound) + } + + fn check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) { + for_tuples!( #( + Tuple::check_out(dest, what, context); )* ); } - fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> XcmResult { + fn deposit_asset(what: &MultiAsset, who: &MultiLocation, context: &XcmContext) -> XcmResult { for_tuples!( #( - match Tuple::deposit_asset(what, who) { + match Tuple::deposit_asset(what, who, context) { Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), r => return r, } )* ); log::trace!( target: "xcm::TransactAsset::deposit_asset", - "did not deposit asset: what: {:?}, who: {:?}", + "did not deposit asset: what: {:?}, who: {:?}, context: {:?}", what, who, + context, ); Err(XcmError::AssetNotFound) } - fn withdraw_asset(what: &MultiAsset, who: &MultiLocation) -> Result { + fn withdraw_asset( + what: &MultiAsset, + who: &MultiLocation, + maybe_context: Option<&XcmContext>, + ) -> Result { for_tuples!( #( - match Tuple::withdraw_asset(what, who) { + match Tuple::withdraw_asset(what, who, maybe_context) { Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), r => return r, } )* ); log::trace!( target: "xcm::TransactAsset::withdraw_asset", - "did not withdraw asset: what: {:?}, who: {:?}", + "did not withdraw asset: what: {:?}, who: {:?}, maybe_context: {:?}", what, who, + maybe_context, ); Err(XcmError::AssetNotFound) } @@ -179,19 +228,21 @@ impl TransactAsset for Tuple { what: &MultiAsset, from: &MultiLocation, to: &MultiLocation, + context: &XcmContext, ) -> Result { for_tuples!( #( - match Tuple::internal_transfer_asset(what, from, to) { + match Tuple::internal_transfer_asset(what, from, to, context) { Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), r => return r, } )* ); log::trace!( target: "xcm::TransactAsset::internal_transfer_asset", - "did not transfer asset: what: {:?}, from: {:?}, to: {:?}", + "did not transfer asset: what: {:?}, from: {:?}, to: {:?}, context: {:?}", what, from, to, + context, ); Err(XcmError::AssetNotFound) } @@ -207,15 +258,35 @@ mod tests { pub struct NotFoundTransactor; impl TransactAsset for NotFoundTransactor { - fn can_check_in(_origin: &MultiLocation, _what: &MultiAsset) -> XcmResult { + fn can_check_in( + _origin: &MultiLocation, + _what: &MultiAsset, + _context: &XcmContext, + ) -> XcmResult { Err(XcmError::AssetNotFound) } - fn deposit_asset(_what: &MultiAsset, _who: &MultiLocation) -> XcmResult { + fn can_check_out( + _dest: &MultiLocation, + _what: &MultiAsset, + _context: &XcmContext, + ) -> XcmResult { Err(XcmError::AssetNotFound) } - fn withdraw_asset(_what: &MultiAsset, _who: &MultiLocation) -> Result { + fn deposit_asset( + _what: &MultiAsset, + _who: &MultiLocation, + _context: &XcmContext, + ) -> XcmResult { + Err(XcmError::AssetNotFound) + } + + fn withdraw_asset( + _what: &MultiAsset, + _who: &MultiLocation, + _context: Option<&XcmContext>, + ) -> Result { Err(XcmError::AssetNotFound) } @@ -223,6 +294,7 @@ mod tests { _what: &MultiAsset, _from: &MultiLocation, _to: &MultiLocation, + _context: &XcmContext, ) -> Result { Err(XcmError::AssetNotFound) } @@ -230,15 +302,35 @@ mod tests { pub struct OverflowTransactor; impl TransactAsset for OverflowTransactor { - fn can_check_in(_origin: &MultiLocation, _what: &MultiAsset) -> XcmResult { + fn can_check_in( + _origin: &MultiLocation, + _what: &MultiAsset, + _context: &XcmContext, + ) -> XcmResult { + Err(XcmError::Overflow) + } + + fn can_check_out( + _dest: &MultiLocation, + _what: &MultiAsset, + _context: &XcmContext, + ) -> XcmResult { Err(XcmError::Overflow) } - fn deposit_asset(_what: &MultiAsset, _who: &MultiLocation) -> XcmResult { + fn deposit_asset( + _what: &MultiAsset, + _who: &MultiLocation, + _context: &XcmContext, + ) -> XcmResult { Err(XcmError::Overflow) } - fn withdraw_asset(_what: &MultiAsset, _who: &MultiLocation) -> Result { + fn withdraw_asset( + _what: &MultiAsset, + _who: &MultiLocation, + _context: Option<&XcmContext>, + ) -> Result { Err(XcmError::Overflow) } @@ -246,6 +338,7 @@ mod tests { _what: &MultiAsset, _from: &MultiLocation, _to: &MultiLocation, + _context: &XcmContext, ) -> Result { Err(XcmError::Overflow) } @@ -253,15 +346,35 @@ mod tests { pub struct SuccessfulTransactor; impl TransactAsset for SuccessfulTransactor { - fn can_check_in(_origin: &MultiLocation, _what: &MultiAsset) -> XcmResult { + fn can_check_in( + _origin: &MultiLocation, + _what: &MultiAsset, + _context: &XcmContext, + ) -> XcmResult { Ok(()) } - fn deposit_asset(_what: &MultiAsset, _who: &MultiLocation) -> XcmResult { + fn can_check_out( + _dest: &MultiLocation, + _what: &MultiAsset, + _context: &XcmContext, + ) -> XcmResult { + Ok(()) + } + + fn deposit_asset( + _what: &MultiAsset, + _who: &MultiLocation, + _context: &XcmContext, + ) -> XcmResult { Ok(()) } - fn withdraw_asset(_what: &MultiAsset, _who: &MultiLocation) -> Result { + fn withdraw_asset( + _what: &MultiAsset, + _who: &MultiLocation, + _context: Option<&XcmContext>, + ) -> Result { Ok(Assets::default()) } @@ -269,6 +382,7 @@ mod tests { _what: &MultiAsset, _from: &MultiLocation, _to: &MultiLocation, + _context: &XcmContext, ) -> Result { Ok(Assets::default()) } @@ -280,7 +394,11 @@ mod tests { (UnimplementedTransactor, NotFoundTransactor, UnimplementedTransactor); assert_eq!( - MultiTransactor::deposit_asset(&(Here, 1).into(), &Here.into()), + MultiTransactor::deposit_asset( + &(Here, 1u128).into(), + &Here.into(), + &XcmContext::with_message_hash([0; 32]), + ), Err(XcmError::AssetNotFound) ); } @@ -289,7 +407,14 @@ mod tests { fn unimplemented_and_not_found_continue_iteration() { type MultiTransactor = (UnimplementedTransactor, NotFoundTransactor, SuccessfulTransactor); - assert_eq!(MultiTransactor::deposit_asset(&(Here, 1).into(), &Here.into()), Ok(()),); + assert_eq!( + MultiTransactor::deposit_asset( + &(Here, 1u128).into(), + &Here.into(), + &XcmContext::with_message_hash([0; 32]), + ), + Ok(()) + ); } #[test] @@ -297,7 +422,11 @@ mod tests { type MultiTransactor = (OverflowTransactor, SuccessfulTransactor); assert_eq!( - MultiTransactor::deposit_asset(&(Here, 1).into(), &Here.into()), + MultiTransactor::deposit_asset( + &(Here, 1u128).into(), + &Here.into(), + &XcmContext::with_message_hash([0; 32]), + ), Err(XcmError::Overflow) ); } @@ -306,6 +435,13 @@ mod tests { fn success_stops_iteration() { type MultiTransactor = (SuccessfulTransactor, OverflowTransactor); - assert_eq!(MultiTransactor::deposit_asset(&(Here, 1).into(), &Here.into()), Ok(()),); + assert_eq!( + MultiTransactor::deposit_asset( + &(Here, 1u128).into(), + &Here.into(), + &XcmContext::with_message_hash([0; 32]), + ), + Ok(()), + ); } } diff --git a/xcm/xcm-simulator/example/Cargo.toml b/xcm/xcm-simulator/example/Cargo.toml index bdc7ea9eb138..54db1b731ca1 100644 --- a/xcm/xcm-simulator/example/Cargo.toml +++ b/xcm/xcm-simulator/example/Cargo.toml @@ -8,14 +8,17 @@ version.workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } scale-info = { version = "2.1.2", features = ["derive"] } +log = { version = "0.4.14", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" } frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-uniques = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-std = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-tracing = { git = "https://github.com/paritytech/substrate", branch = "master" } xcm = { path = "../../" } xcm-simulator = { path = "../" } @@ -25,3 +28,17 @@ pallet-xcm = { path = "../../pallet-xcm" } polkadot-core-primitives = { path = "../../../core-primitives" } polkadot-runtime-parachains = { path = "../../../runtime/parachains" } polkadot-parachain = { path = "../../../parachain" } + +[features] +default = [] +runtime-benchmarks = [ + "frame-system/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-uniques/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", + "polkadot-runtime-parachains/runtime-benchmarks", + "polkadot-parachain/runtime-benchmarks", +] diff --git a/xcm/xcm-simulator/example/src/lib.rs b/xcm/xcm-simulator/example/src/lib.rs index 1e4f96cb2282..0ddaf6337f5b 100644 --- a/xcm/xcm-simulator/example/src/lib.rs +++ b/xcm/xcm-simulator/example/src/lib.rs @@ -17,8 +17,9 @@ mod parachain; mod relay_chain; -use polkadot_parachain::primitives::Id as ParaId; -use sp_runtime::traits::AccountIdConversion; +use frame_support::sp_tracing; +use xcm::prelude::*; +use xcm_executor::traits::Convert; 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]); @@ -60,8 +61,29 @@ decl_test_network! { } } -pub fn para_account_id(id: u32) -> relay_chain::AccountId { - ParaId::from(id).into_account_truncating() +pub fn parent_account_id() -> parachain::AccountId { + let location = (Parent,); + parachain::LocationToAccountId::convert(location.into()).unwrap() +} + +pub fn child_account_id(para: u32) -> relay_chain::AccountId { + let location = (Parachain(para),); + relay_chain::LocationToAccountId::convert(location.into()).unwrap() +} + +pub fn child_account_account_id(para: u32, who: sp_runtime::AccountId32) -> relay_chain::AccountId { + let location = (Parachain(para), AccountId32 { network: None, id: who.into() }); + relay_chain::LocationToAccountId::convert(location.into()).unwrap() +} + +pub fn sibling_account_account_id(para: u32, who: sp_runtime::AccountId32) -> parachain::AccountId { + let location = (Parent, Parachain(para), AccountId32 { network: None, id: who.into() }); + parachain::LocationToAccountId::convert(location.into()).unwrap() +} + +pub fn parent_account_account_id(who: sp_runtime::AccountId32) -> parachain::AccountId { + let location = (Parent, AccountId32 { network: None, id: who.into() }); + parachain::LocationToAccountId::convert(location.into()).unwrap() } pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { @@ -69,12 +91,15 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(ALICE, INITIAL_BALANCE)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(ALICE, INITIAL_BALANCE), (parent_account_id(), INITIAL_BALANCE)], + } + .assimilate_storage(&mut t) + .unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| { + sp_tracing::try_init_simple(); System::set_block_number(1); MsgQueue::set_para_id(para_id.into()); }); @@ -82,18 +107,26 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { } pub fn relay_ext() -> sp_io::TestExternalities { - use relay_chain::{Runtime, System}; + use relay_chain::{Runtime, RuntimeOrigin, System, Uniques}; let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); pallet_balances::GenesisConfig:: { - balances: vec![(ALICE, INITIAL_BALANCE), (para_account_id(1), INITIAL_BALANCE)], + balances: vec![ + (ALICE, INITIAL_BALANCE), + (child_account_id(1), INITIAL_BALANCE), + (child_account_id(2), INITIAL_BALANCE), + ], } .assimilate_storage(&mut t) .unwrap(); let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); + ext.execute_with(|| { + System::set_block_number(1); + assert_eq!(Uniques::force_create(RuntimeOrigin::root(), 1, ALICE, true), Ok(())); + assert_eq!(Uniques::mint(RuntimeOrigin::signed(ALICE), 1, 42, child_account_id(1)), Ok(())); + }); ext } @@ -105,8 +138,8 @@ mod tests { use super::*; use codec::Encode; - use frame_support::assert_ok; - use xcm::latest::prelude::*; + use frame_support::{assert_ok, weights::Weight}; + use xcm::latest::QueryResponseInfo; use xcm_simulator::TestExt; // Helper function for forming buy execution message @@ -114,6 +147,13 @@ mod tests { BuyExecution { fees: fees.into(), weight_limit: Unlimited } } + #[test] + fn remote_account_ids_work() { + child_account_account_id(1, ALICE); + sibling_account_account_id(1, ALICE); + parent_account_account_id(ALICE); + } + #[test] fn dmp() { MockNet::reset(); @@ -126,8 +166,8 @@ mod tests { Here, Parachain(1), Xcm(vec![Transact { - origin_type: OriginKind::SovereignAccount, - require_weight_at_most: INITIAL_BALANCE as u64, + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), call: remark.encode().into(), }]), )); @@ -154,8 +194,8 @@ mod tests { Here, Parent, Xcm(vec![Transact { - origin_type: OriginKind::SovereignAccount, - require_weight_at_most: INITIAL_BALANCE as u64, + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), call: remark.encode().into(), }]), )); @@ -182,8 +222,8 @@ mod tests { Here, (Parent, Parachain(2)), Xcm(vec![Transact { - origin_type: OriginKind::SovereignAccount, - require_weight_at_most: INITIAL_BALANCE as u64, + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), call: remark.encode().into(), }]), )); @@ -207,13 +247,13 @@ mod tests { Relay::execute_with(|| { assert_ok!(RelayChainPalletXcm::reserve_transfer_assets( relay_chain::RuntimeOrigin::signed(ALICE), - Box::new(X1(Parachain(1)).into().into()), - Box::new(X1(AccountId32 { network: Any, id: ALICE.into() }).into().into()), + Box::new(Parachain(1).into()), + Box::new(AccountId32 { network: None, id: ALICE.into() }.into()), Box::new((Here, withdraw_amount).into()), 0, )); assert_eq!( - parachain::Balances::free_balance(¶_account_id(1)), + parachain::Balances::free_balance(&child_account_id(1)), INITIAL_BALANCE + withdraw_amount ); }); @@ -227,6 +267,271 @@ mod tests { }); } + #[test] + fn remote_locking() { + MockNet::reset(); + + let locked_amount = 100; + + ParaB::execute_with(|| { + let message = Xcm(vec![LockAsset { + asset: (Here, locked_amount).into(), + unlocker: (Parachain(1),).into(), + }]); + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); + }); + + Relay::execute_with(|| { + use pallet_balances::{BalanceLock, Reasons}; + assert_eq!( + relay_chain::Balances::locks(&child_account_id(2)), + vec![BalanceLock { + id: *b"py/xcmlk", + amount: locked_amount, + reasons: Reasons::All + }] + ); + }); + + ParaA::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![NoteUnlockable { + owner: (Parent, Parachain(2)).into(), + asset: (Parent, locked_amount).into() + }])] + ); + }); + } + + /// Scenario: + /// A parachain transfers an NFT resident on the relay chain to another parachain account. + /// + /// Asserts that the parachain accounts are updated as expected. + #[test] + fn withdraw_and_deposit_nft() { + MockNet::reset(); + + Relay::execute_with(|| { + assert_eq!(relay_chain::Uniques::owner(1, 42), Some(child_account_id(1))); + }); + + ParaA::execute_with(|| { + let message = Xcm(vec![TransferAsset { + assets: (GeneralIndex(1), 42u32).into(), + beneficiary: Parachain(2).into(), + }]); + // Send withdraw and deposit + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message)); + }); + + Relay::execute_with(|| { + assert_eq!(relay_chain::Uniques::owner(1, 42), Some(child_account_id(2))); + }); + } + + /// Scenario: + /// The relay-chain teleports an NFT to a parachain. + /// + /// Asserts that the parachain accounts are updated as expected. + #[test] + fn teleport_nft() { + MockNet::reset(); + + Relay::execute_with(|| { + // Mint the NFT (1, 69) and give it to our "parachain#1 alias". + assert_ok!(relay_chain::Uniques::mint( + relay_chain::RuntimeOrigin::signed(ALICE), + 1, + 69, + child_account_account_id(1, ALICE), + )); + // The parachain#1 alias of Alice is what must hold it on the Relay-chain for it to be + // withdrawable by Alice on the parachain. + assert_eq!( + relay_chain::Uniques::owner(1, 69), + Some(child_account_account_id(1, ALICE)) + ); + }); + ParaA::execute_with(|| { + assert_ok!(parachain::ForeignUniques::force_create( + parachain::RuntimeOrigin::root(), + (Parent, GeneralIndex(1)).into(), + ALICE, + false, + )); + assert_eq!( + parachain::ForeignUniques::owner((Parent, GeneralIndex(1)).into(), 69u32.into()), + None, + ); + assert_eq!(parachain::Balances::reserved_balance(&ALICE), 0); + + // IRL Alice would probably just execute this locally on the Relay-chain, but we can't + // easily do that here since we only send between chains. + let message = Xcm(vec![ + WithdrawAsset((GeneralIndex(1), 69u32).into()), + InitiateTeleport { + assets: AllCounted(1).into(), + dest: Parachain(1).into(), + xcm: Xcm(vec![DepositAsset { + assets: AllCounted(1).into(), + beneficiary: (AccountId32 { id: ALICE.into(), network: None },).into(), + }]), + }, + ]); + // Send teleport + let alice = AccountId32 { id: ALICE.into(), network: None }; + assert_ok!(ParachainPalletXcm::send_xcm(alice, Parent, message)); + }); + ParaA::execute_with(|| { + assert_eq!( + parachain::ForeignUniques::owner((Parent, GeneralIndex(1)).into(), 69u32.into()), + Some(ALICE), + ); + assert_eq!(parachain::Balances::reserved_balance(&ALICE), 1000); + }); + Relay::execute_with(|| { + assert_eq!(relay_chain::Uniques::owner(1, 69), None); + }); + } + + /// Scenario: + /// The relay-chain transfers an NFT into a parachain's sovereign account, who then mints a + /// trustless-backed-derivated locally. + /// + /// Asserts that the parachain accounts are updated as expected. + #[test] + fn reserve_asset_transfer_nft() { + sp_tracing::init_for_tests(); + MockNet::reset(); + + Relay::execute_with(|| { + assert_ok!(relay_chain::Uniques::force_create( + relay_chain::RuntimeOrigin::root(), + 2, + ALICE, + false + )); + assert_ok!(relay_chain::Uniques::mint( + relay_chain::RuntimeOrigin::signed(ALICE), + 2, + 69, + child_account_account_id(1, ALICE) + )); + assert_eq!( + relay_chain::Uniques::owner(2, 69), + Some(child_account_account_id(1, ALICE)) + ); + }); + ParaA::execute_with(|| { + assert_ok!(parachain::ForeignUniques::force_create( + parachain::RuntimeOrigin::root(), + (Parent, GeneralIndex(2)).into(), + ALICE, + false, + )); + assert_eq!( + parachain::ForeignUniques::owner((Parent, GeneralIndex(2)).into(), 69u32.into()), + None, + ); + assert_eq!(parachain::Balances::reserved_balance(&ALICE), 0); + + let message = Xcm(vec![ + WithdrawAsset((GeneralIndex(2), 69u32).into()), + DepositReserveAsset { + assets: AllCounted(1).into(), + dest: Parachain(1).into(), + xcm: Xcm(vec![DepositAsset { + assets: AllCounted(1).into(), + beneficiary: (AccountId32 { id: ALICE.into(), network: None },).into(), + }]), + }, + ]); + // Send transfer + let alice = AccountId32 { id: ALICE.into(), network: None }; + assert_ok!(ParachainPalletXcm::send_xcm(alice, Parent, message)); + }); + ParaA::execute_with(|| { + log::debug!(target: "xcm-exceutor", "Hello"); + assert_eq!( + parachain::ForeignUniques::owner((Parent, GeneralIndex(2)).into(), 69u32.into()), + Some(ALICE), + ); + assert_eq!(parachain::Balances::reserved_balance(&ALICE), 1000); + }); + + Relay::execute_with(|| { + assert_eq!(relay_chain::Uniques::owner(2, 69), Some(child_account_id(1))); + }); + } + + /// Scenario: + /// The relay-chain creates an asset class on a parachain and then Alice transfers her NFT into + /// that parachain's sovereign account, who then mints a trustless-backed-derivative locally. + /// + /// Asserts that the parachain accounts are updated as expected. + #[test] + fn reserve_asset_class_create_and_reserve_transfer() { + MockNet::reset(); + + Relay::execute_with(|| { + assert_ok!(relay_chain::Uniques::force_create( + relay_chain::RuntimeOrigin::root(), + 2, + ALICE, + false + )); + assert_ok!(relay_chain::Uniques::mint( + relay_chain::RuntimeOrigin::signed(ALICE), + 2, + 69, + child_account_account_id(1, ALICE) + )); + assert_eq!( + relay_chain::Uniques::owner(2, 69), + Some(child_account_account_id(1, ALICE)) + ); + + let message = Xcm(vec![Transact { + origin_kind: OriginKind::Xcm, + require_weight_at_most: Weight::from_parts(1_000_000_000, 1024 * 1024), + call: parachain::RuntimeCall::from( + pallet_uniques::Call::::create { + collection: (Parent, 2u64).into(), + admin: parent_account_id(), + }, + ) + .encode() + .into(), + }]); + // Send creation. + assert_ok!(RelayChainPalletXcm::send_xcm(Here, Parachain(1), message)); + }); + ParaA::execute_with(|| { + // Then transfer + let message = Xcm(vec![ + WithdrawAsset((GeneralIndex(2), 69u32).into()), + DepositReserveAsset { + assets: AllCounted(1).into(), + dest: Parachain(1).into(), + xcm: Xcm(vec![DepositAsset { + assets: AllCounted(1).into(), + beneficiary: (AccountId32 { id: ALICE.into(), network: None },).into(), + }]), + }, + ]); + let alice = AccountId32 { id: ALICE.into(), network: None }; + assert_ok!(ParachainPalletXcm::send_xcm(alice, Parent, message)); + }); + ParaA::execute_with(|| { + assert_eq!(parachain::Balances::reserved_balance(&parent_account_id()), 1000); + assert_eq!( + parachain::ForeignUniques::collection_owner((Parent, 2u64).into()), + Some(parent_account_id()) + ); + }); + } + /// Scenario: /// A parachain transfers funds on the relay chain to another parachain account. /// @@ -241,11 +546,7 @@ mod tests { 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(), - }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: Parachain(2).into() }, ]); // Send withdraw and deposit assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); @@ -253,10 +554,13 @@ mod tests { Relay::execute_with(|| { assert_eq!( - relay_chain::Balances::free_balance(para_account_id(1)), + relay_chain::Balances::free_balance(child_account_id(1)), INITIAL_BALANCE - send_amount ); - assert_eq!(relay_chain::Balances::free_balance(para_account_id(2)), send_amount); + assert_eq!( + relay_chain::Balances::free_balance(child_account_id(2)), + INITIAL_BALANCE + send_amount + ); }); } @@ -277,16 +581,14 @@ mod tests { let message = Xcm(vec![ WithdrawAsset((Here, send_amount).into()), buy_execution((Here, send_amount)), - DepositAsset { + DepositAsset { assets: AllCounted(1).into(), beneficiary: Parachain(2).into() }, + ReportHolding { + response_info: QueryResponseInfo { + destination: Parachain(1).into(), + query_id: query_id_set, + max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), + }, 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 @@ -297,11 +599,14 @@ mod tests { Relay::execute_with(|| { // Withdraw executed assert_eq!( - relay_chain::Balances::free_balance(para_account_id(1)), + relay_chain::Balances::free_balance(child_account_id(1)), INITIAL_BALANCE - send_amount ); // Deposit executed - assert_eq!(relay_chain::Balances::free_balance(para_account_id(2)), send_amount); + assert_eq!( + relay_chain::Balances::free_balance(child_account_id(2)), + INITIAL_BALANCE + send_amount + ); }); // Check that QueryResponse message was received @@ -311,7 +616,8 @@ mod tests { vec![Xcm(vec![QueryResponse { query_id: query_id_set, response: Response::Assets(MultiAssets::new()), - max_weight: 1_000_000_000, + max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), + querier: Some(Here.into()), }])], ); }); diff --git a/xcm/xcm-simulator/example/src/parachain.rs b/xcm/xcm-simulator/example/src/parachain.rs index d638867c281f..3ef6c4b9d651 100644 --- a/xcm/xcm-simulator/example/src/parachain.rs +++ b/xcm/xcm-simulator/example/src/parachain.rs @@ -19,7 +19,7 @@ use codec::{Decode, Encode}; use frame_support::{ construct_runtime, parameter_types, - traits::{Everything, Nothing}, + traits::{EnsureOrigin, EnsureOriginWithArg, Everything, EverythingBut, Nothing}, weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, }; use sp_core::H256; @@ -37,12 +37,22 @@ use polkadot_parachain::primitives::{ }; use xcm::{latest::prelude::*, VersionedXcm}; use xcm_builder::{ - AccountId32Aliases, AllowUnpaidExecutionFrom, CurrencyAdapter as XcmCurrencyAdapter, - EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, IsConcrete, LocationInverter, - NativeAsset, ParentIsPreset, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, + Account32Hash, AccountId32Aliases, AllowUnpaidExecutionFrom, ConvertedConcreteId, + CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, + IsConcrete, NativeAsset, NoChecking, NonFungiblesAdapter, ParentIsPreset, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, +}; +use xcm_executor::{ + traits::{Convert, JustTry}, + Config, XcmExecutor, }; -use xcm_executor::{Config, XcmExecutor}; + +pub type SovereignAccountOf = ( + SiblingParachainConvertsVia, + AccountId32Aliases, + ParentIsPreset, +); pub type AccountId = AccountId32; pub type Balance = u128; @@ -96,6 +106,62 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; } +#[cfg(feature = "runtime-benchmarks")] +pub struct UniquesHelper; +#[cfg(feature = "runtime-benchmarks")] +impl pallet_uniques::BenchmarkHelper for UniquesHelper { + fn collection(i: u16) -> MultiLocation { + GeneralIndex(i as u128).into() + } + fn item(i: u16) -> AssetInstance { + AssetInstance::Index(i as u128) + } +} + +impl pallet_uniques::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type CollectionId = MultiLocation; + type ItemId = AssetInstance; + type Currency = Balances; + type CreateOrigin = ForeignCreators; + type ForceOrigin = frame_system::EnsureRoot; + type CollectionDeposit = frame_support::traits::ConstU128<1_000>; + type ItemDeposit = frame_support::traits::ConstU128<1_000>; + type MetadataDepositBase = frame_support::traits::ConstU128<1_000>; + type AttributeDepositBase = frame_support::traits::ConstU128<1_000>; + type DepositPerByte = frame_support::traits::ConstU128<1>; + type StringLimit = frame_support::traits::ConstU32<64>; + type KeyLimit = frame_support::traits::ConstU32<64>; + type ValueLimit = frame_support::traits::ConstU32<128>; + type Locker = (); + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type Helper = UniquesHelper; +} + +// `EnsureOriginWithArg` impl for `CreateOrigin` which allows only XCM origins +// which are locations containing the class location. +pub struct ForeignCreators; +impl EnsureOriginWithArg for ForeignCreators { + type Success = AccountId; + + fn try_origin( + o: RuntimeOrigin, + a: &MultiLocation, + ) -> sp_std::result::Result { + let origin_location = pallet_xcm::EnsureXcm::::try_origin(o.clone())?; + if !a.starts_with(&origin_location) { + return Err(o) + } + SovereignAccountOf::convert(origin_location).map_err(|_| o) + } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin(a: &MultiLocation) -> RuntimeOrigin { + pallet_xcm::Origin::Xcm(a.clone()).into() + } +} + parameter_types! { pub const ReservedXcmpWeight: Weight = Weight::from_ref_time(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4)); pub const ReservedDmpWeight: Weight = Weight::from_ref_time(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4)); @@ -104,13 +170,14 @@ parameter_types! { parameter_types! { pub const KsmLocation: MultiLocation = MultiLocation::parent(); pub const RelayNetwork: NetworkId = NetworkId::Kusama; - pub Ancestry: MultiLocation = Parachain(MsgQueue::parachain_id().into()).into(); + pub UniversalLocation: InteriorMultiLocation = Parachain(MsgQueue::parachain_id().into()).into(); } pub type LocationToAccountId = ( ParentIsPreset, SiblingParachainConvertsVia, AccountId32Aliases, + Account32Hash<(), AccountId>, ); pub type XcmOriginToCallOrigin = ( @@ -120,33 +187,62 @@ pub type XcmOriginToCallOrigin = ( ); parameter_types! { - pub const UnitWeightCost: u64 = 1; - pub KsmPerSecond: (AssetId, u128) = (Concrete(Parent.into()), 1); + pub const UnitWeightCost: Weight = Weight::from_parts(1, 1); + pub KsmPerSecondPerByte: (AssetId, u128, u128) = (Concrete(Parent.into()), 1, 1); pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; + pub ForeignPrefix: MultiLocation = (Parent,).into(); } -pub type LocalAssetTransactor = - XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>; +pub type LocalAssetTransactor = ( + XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>, + NonFungiblesAdapter< + ForeignUniques, + ConvertedConcreteId, + SovereignAccountOf, + AccountId, + NoChecking, + (), + >, +); pub type XcmRouter = super::ParachainXcmRouter; pub type Barrier = AllowUnpaidExecutionFrom; +parameter_types! { + pub NftCollectionOne: MultiAssetFilter + = Wild(AllOf { fun: WildNonFungible, id: Concrete((Parent, GeneralIndex(1)).into()) }); + pub NftCollectionOneForRelay: (MultiAssetFilter, MultiLocation) + = (NftCollectionOne::get(), (Parent,).into()); +} +pub type TrustedTeleporters = xcm_builder::Case; +pub type TrustedReserves = EverythingBut>; + pub struct XcmConfig; impl Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; type AssetTransactor = LocalAssetTransactor; type OriginConverter = XcmOriginToCallOrigin; - type IsReserve = NativeAsset; - type IsTeleporter = (); - type LocationInverter = LocationInverter; + type IsReserve = (NativeAsset, TrustedReserves); + type IsTeleporter = TrustedTeleporters; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; - type Trader = FixedRateOfFungible; + type Trader = FixedRateOfFungible; type ResponseHandler = (); type AssetTrap = (); + type AssetLocker = (); + type AssetExchanger = (); type AssetClaims = (); type SubscriptionService = (); + type PalletInstancesInfo = (); + type FeeManager = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; } #[frame_support::pallet] @@ -219,17 +315,16 @@ pub mod mock_msg_queue { max_weight: Weight, ) -> Result { let hash = Encode::using_encoded(&xcm, T::Hashing::hash); + let message_hash = Encode::using_encoded(&xcm, sp_io::hashing::blake2_256); 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.ref_time()) { + let location = (Parent, Parachain(sender.into())); + match T::XcmExecutor::execute_xcm(location, xcm, message_hash, max_weight) { Outcome::Error(e) => (Err(e.clone()), Event::Fail(Some(hash), e)), - Outcome::Complete(w) => - (Ok(Weight::from_ref_time(w)), Event::Success(Some(hash))), + 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(Weight::from_ref_time(w)), Event::Fail(Some(hash), e)), + Outcome::Incomplete(w, e) => (Ok(w), Event::Fail(Some(hash), e)), } }, Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))), @@ -271,20 +366,18 @@ pub mod mock_msg_queue { ) -> 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 { + let maybe_versioned = VersionedXcm::::decode(&mut &data[..]); + match maybe_versioned { 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.ref_time()); - >::append(x); - Self::deposit_event(Event::ExecutedDownward(id, outcome)); + Ok(versioned) => match Xcm::try_from(versioned) { + Err(()) => Self::deposit_event(Event::UnsupportedVersion(id)), + Ok(x) => { + let outcome = T::XcmExecutor::execute_xcm(Parent, x.clone(), id, limit); + >::append(x); + Self::deposit_event(Event::ExecutedDownward(id, outcome)); + }, }, } } @@ -300,6 +393,11 @@ impl mock_msg_queue::Config for Runtime { pub type LocalOriginToLocation = SignedToAccountId32; +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Parent.into()); +} + impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type SendXcmOrigin = EnsureXcmOrigin; @@ -310,11 +408,19 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Nothing; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = frame_support::traits::ConstU32<8>; + type WeightInfo = pallet_xcm::TestWeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -330,5 +436,6 @@ construct_runtime!( Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, MsgQueue: mock_msg_queue::{Pallet, Storage, Event}, PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin}, + ForeignUniques: pallet_uniques::{Pallet, Call, Storage, Event}, } ); diff --git a/xcm/xcm-simulator/example/src/relay_chain.rs b/xcm/xcm-simulator/example/src/relay_chain.rs index 3a5d9e229621..9e095270e673 100644 --- a/xcm/xcm-simulator/example/src/relay_chain.rs +++ b/xcm/xcm-simulator/example/src/relay_chain.rs @@ -18,7 +18,8 @@ use frame_support::{ construct_runtime, parameter_types, - traits::{Everything, Nothing}, + traits::{AsEnsureOriginWithArg, Everything, Nothing}, + weights::Weight, }; use sp_core::H256; use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; @@ -27,12 +28,13 @@ use polkadot_parachain::primitives::Id as ParaId; use polkadot_runtime_parachains::{configuration, origin, shared, ump}; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowUnpaidExecutionFrom, ChildParachainAsNative, - ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete, - LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, + Account32Hash, AccountId32Aliases, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, + ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, + ConvertedConcreteId, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, + FixedWeightBounds, IsConcrete, NoChecking, NonFungiblesAdapter, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, }; -use xcm_executor::{Config, XcmExecutor}; +use xcm_executor::{traits::JustTry, Config, XcmExecutor}; pub type AccountId = AccountId32; pub type Balance = u128; @@ -86,6 +88,27 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; } +impl pallet_uniques::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type CollectionId = u32; + type ItemId = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type CollectionDeposit = frame_support::traits::ConstU128<1_000>; + type ItemDeposit = frame_support::traits::ConstU128<1_000>; + type MetadataDepositBase = frame_support::traits::ConstU128<1_000>; + type AttributeDepositBase = frame_support::traits::ConstU128<1_000>; + type DepositPerByte = frame_support::traits::ConstU128<1>; + type StringLimit = frame_support::traits::ConstU32<64>; + type KeyLimit = frame_support::traits::ConstU32<64>; + type ValueLimit = frame_support::traits::ConstU32<128>; + type Locker = (); + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); +} + impl shared::Config for Runtime {} impl configuration::Config for Runtime { @@ -93,30 +116,44 @@ impl configuration::Config for Runtime { } 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 const TokenLocation: MultiLocation = Here.into_location(); + pub RelayNetwork: NetworkId = ByGenesis([0; 32]); + pub const AnyNetwork: Option = None; + pub UniversalLocation: InteriorMultiLocation = Here; pub UnitWeightCost: u64 = 1_000; } -pub type SovereignAccountOf = - (ChildParachainConvertsVia, AccountId32Aliases); +pub type LocationToAccountId = ( + ChildParachainConvertsVia, + AccountId32Aliases, + Account32Hash<(), AccountId>, +); -pub type LocalAssetTransactor = - XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; +pub type LocalAssetTransactor = ( + XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>, + NonFungiblesAdapter< + Uniques, + ConvertedConcreteId, JustTry>, + LocationToAccountId, + AccountId, + NoChecking, + (), + >, +); type LocalOriginConverter = ( - SovereignSignedViaLocation, + SovereignSignedViaLocation, ChildParachainAsNative, - SignedAccountId32AsNative, + SignedAccountId32AsNative, ChildSystemParachainAsSuperuser, ); parameter_types! { - pub const BaseXcmWeight: u64 = 1_000; - pub KsmPerSecond: (AssetId, u128) = (Concrete(KsmLocation::get()), 1); + pub const BaseXcmWeight: Weight = Weight::from_parts(1_000, 1_000); + pub TokensPerSecondPerByte: (AssetId, u128, u128) = + (Concrete(TokenLocation::get()), 1_000_000_000_000, 1024 * 1024); pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } pub type XcmRouter = super::RelayChainXcmRouter; @@ -130,17 +167,31 @@ impl Config for XcmConfig { type OriginConverter = LocalOriginConverter; type IsReserve = (); type IsTeleporter = (); - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; - type Trader = FixedRateOfFungible; + type Trader = FixedRateOfFungible; type ResponseHandler = (); type AssetTrap = (); + type AssetLocker = XcmPallet; + type AssetExchanger = (); type AssetClaims = (); type SubscriptionService = (); + type PalletInstancesInfo = (); + type FeeManager = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; } -pub type LocalOriginToLocation = SignedToAccountId32; +pub type LocalOriginToLocation = SignedToAccountId32; + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Parachain(1).into()); +} impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -153,11 +204,19 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = IsConcrete; + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = frame_support::traits::ConstU32<8>; + type WeightInfo = pallet_xcm::TestWeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; } parameter_types! { @@ -188,5 +247,6 @@ construct_runtime!( ParasOrigin: origin::{Pallet, Origin}, ParasUmp: ump::{Pallet, Call, Storage, Event}, XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin}, + Uniques: pallet_uniques::{Pallet, Call, Storage, Event}, } ); diff --git a/xcm/xcm-simulator/fuzzer/Cargo.toml b/xcm/xcm-simulator/fuzzer/Cargo.toml index 5a24c34f2b30..25767fb429b0 100644 --- a/xcm/xcm-simulator/fuzzer/Cargo.toml +++ b/xcm/xcm-simulator/fuzzer/Cargo.toml @@ -27,6 +27,11 @@ polkadot-core-primitives = { path = "../../../core-primitives" } polkadot-runtime-parachains = { path = "../../../runtime/parachains" } polkadot-parachain = { path = "../../../parachain" } +[features] +runtime-benchmarks = [ + "pallet-xcm/runtime-benchmarks", +] + [[bin]] path = "src/fuzz.rs" name = "xcm-fuzzer" diff --git a/xcm/xcm-simulator/fuzzer/src/parachain.rs b/xcm/xcm-simulator/fuzzer/src/parachain.rs index ce2a2698d00c..c2940ef3c513 100644 --- a/xcm/xcm-simulator/fuzzer/src/parachain.rs +++ b/xcm/xcm-simulator/fuzzer/src/parachain.rs @@ -38,9 +38,9 @@ use polkadot_parachain::primitives::{ use xcm::{latest::prelude::*, VersionedXcm}; use xcm_builder::{ AccountId32Aliases, AllowUnpaidExecutionFrom, CurrencyAdapter as XcmCurrencyAdapter, - EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, IsConcrete, LocationInverter, - NativeAsset, ParentIsPreset, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, + EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, IsConcrete, NativeAsset, + ParentIsPreset, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, }; use xcm_executor::{Config, XcmExecutor}; @@ -104,7 +104,7 @@ parameter_types! { parameter_types! { pub const KsmLocation: MultiLocation = MultiLocation::parent(); pub const RelayNetwork: NetworkId = NetworkId::Kusama; - pub Ancestry: MultiLocation = Parachain(MsgQueue::parachain_id().into()).into(); + pub UniversalLocation: InteriorMultiLocation = Parachain(MsgQueue::parachain_id().into()).into(); } pub type LocationToAccountId = ( @@ -120,9 +120,10 @@ pub type XcmOriginToCallOrigin = ( ); parameter_types! { - pub const UnitWeightCost: u64 = 1; - pub KsmPerSecond: (AssetId, u128) = (Concrete(Parent.into()), 1); + pub const UnitWeightCost: Weight = Weight::from_parts(1, 1); + pub KsmPerSecondPerByte: (AssetId, u128, u128) = (Concrete(Parent.into()), 1, 1); pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } pub type LocalAssetTransactor = @@ -139,14 +140,23 @@ impl Config for XcmConfig { type OriginConverter = XcmOriginToCallOrigin; type IsReserve = NativeAsset; type IsTeleporter = (); - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; - type Trader = FixedRateOfFungible; + type Trader = FixedRateOfFungible; type ResponseHandler = (); type AssetTrap = (); + type AssetLocker = (); + type AssetExchanger = (); type AssetClaims = (); type SubscriptionService = (); + type PalletInstancesInfo = (); + type FeeManager = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; } #[frame_support::pallet] @@ -219,17 +229,16 @@ pub mod mock_msg_queue { max_weight: Weight, ) -> Result { let hash = Encode::using_encoded(&xcm, T::Hashing::hash); + let message_hash = xcm.using_encoded(sp_io::hashing::blake2_256); let (result, event) = match Xcm::::try_from(xcm) { Ok(xcm) => { let location = MultiLocation::new(1, X1(Parachain(sender.into()))); - match T::XcmExecutor::execute_xcm(location, xcm, max_weight.ref_time()) { + match T::XcmExecutor::execute_xcm(location, xcm, message_hash, max_weight) { Outcome::Error(e) => (Err(e.clone()), Event::Fail(Some(hash), e)), - Outcome::Complete(w) => - (Ok(Weight::from_ref_time(w)), Event::Success(Some(hash))), + 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(Weight::from_ref_time(w)), Event::Fail(Some(hash), e)), + Outcome::Incomplete(w, e) => (Ok(w), Event::Fail(Some(hash), e)), } }, Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))), @@ -281,8 +290,7 @@ pub mod mock_msg_queue { Self::deposit_event(Event::UnsupportedVersion(id)); }, Ok(Ok(x)) => { - let outcome = - T::XcmExecutor::execute_xcm(Parent, x.clone(), limit.ref_time()); + let outcome = T::XcmExecutor::execute_xcm(Parent, x.clone(), id, limit); >::append(x); Self::deposit_event(Event::ExecutedDownward(id, outcome)); }, @@ -300,6 +308,11 @@ impl mock_msg_queue::Config for Runtime { pub type LocalOriginToLocation = SignedToAccountId32; +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Parent.into()); +} + impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type SendXcmOrigin = EnsureXcmOrigin; @@ -310,11 +323,19 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Nothing; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = frame_support::traits::ConstU32<8>; + type WeightInfo = pallet_xcm::TestWeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; diff --git a/xcm/xcm-simulator/fuzzer/src/relay_chain.rs b/xcm/xcm-simulator/fuzzer/src/relay_chain.rs index 3a5d9e229621..fe08a056cdc1 100644 --- a/xcm/xcm-simulator/fuzzer/src/relay_chain.rs +++ b/xcm/xcm-simulator/fuzzer/src/relay_chain.rs @@ -19,6 +19,7 @@ use frame_support::{ construct_runtime, parameter_types, traits::{Everything, Nothing}, + weights::Weight, }; use sp_core::H256; use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; @@ -30,7 +31,7 @@ use xcm_builder::{ AccountId32Aliases, AllowUnpaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete, - LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, }; use xcm_executor::{Config, XcmExecutor}; @@ -93,30 +94,30 @@ impl configuration::Config for Runtime { } 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: u64 = 1_000; + pub const TokenLocation: MultiLocation = Here.into_location(); + pub const ThisNetwork: NetworkId = NetworkId::ByGenesis([0; 32]); + pub const AnyNetwork: Option = None; + pub const UniversalLocation: InteriorMultiLocation = Here; } pub type SovereignAccountOf = - (ChildParachainConvertsVia, AccountId32Aliases); + (ChildParachainConvertsVia, AccountId32Aliases); pub type LocalAssetTransactor = - XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; + XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; type LocalOriginConverter = ( SovereignSignedViaLocation, ChildParachainAsNative, - SignedAccountId32AsNative, + SignedAccountId32AsNative, ChildSystemParachainAsSuperuser, ); parameter_types! { - pub const BaseXcmWeight: u64 = 1_000; - pub KsmPerSecond: (AssetId, u128) = (Concrete(KsmLocation::get()), 1); + pub const BaseXcmWeight: Weight = Weight::from_parts(1_000, 1_000); + pub KsmPerSecondPerByte: (AssetId, u128, u128) = (Concrete(TokenLocation::get()), 1, 1); pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } pub type XcmRouter = super::RelayChainXcmRouter; @@ -130,17 +131,31 @@ impl Config for XcmConfig { type OriginConverter = LocalOriginConverter; type IsReserve = (); type IsTeleporter = (); - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; - type Trader = FixedRateOfFungible; + type Trader = FixedRateOfFungible; type ResponseHandler = (); type AssetTrap = (); + type AssetLocker = (); + type AssetExchanger = (); type AssetClaims = (); type SubscriptionService = (); + type PalletInstancesInfo = (); + type FeeManager = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; } -pub type LocalOriginToLocation = SignedToAccountId32; +pub type LocalOriginToLocation = SignedToAccountId32; + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Parachain(1).into()); +} impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -153,11 +168,19 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = SovereignAccountOf; + type MaxLockers = frame_support::traits::ConstU32<8>; + type WeightInfo = pallet_xcm::TestWeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; } parameter_types! { diff --git a/xcm/xcm-simulator/src/lib.rs b/xcm/xcm-simulator/src/lib.rs index a8d602037cd5..f87aae3f29a7 100644 --- a/xcm/xcm-simulator/src/lib.rs +++ b/xcm/xcm-simulator/src/lib.rs @@ -20,7 +20,7 @@ pub use codec::Encode; pub use paste; pub use frame_support::{traits::Get, weights::Weight}; -pub use sp_io::TestExternalities; +pub use sp_io::{hashing::blake2_256, TestExternalities}; pub use sp_std::{cell::RefCell, collections::vec_deque::VecDeque, marker::PhantomData}; pub use polkadot_core_primitives::BlockNumber as RelayBlockNumber; @@ -77,6 +77,10 @@ pub fn encode_xcm(message: Xcm<()>, message_kind: MessageKind) -> Vec { } } +pub fn fake_message_hash(message: &Xcm) -> XcmHash { + message.using_encoded(blake2_256) +} + /// The macro is implementing upward message passing(UMP) for the provided relay /// chain struct. The struct has to provide the XCM configuration for the relay /// chain. @@ -257,6 +261,7 @@ macro_rules! decl_test_network { parachains = vec![ $( ($para_id:expr, $parachain:ty), )* ], } ) => { + use $crate::Encode; pub struct $name; impl $name { @@ -293,7 +298,7 @@ macro_rules! decl_test_network { $crate::Weight::MAX, ); if let Err((id, required)) = r { - return Err($crate::XcmError::WeightLimitReached(required.ref_time())); + return Err($crate::XcmError::WeightLimitReached(required)); } }, $( @@ -343,45 +348,65 @@ macro_rules! decl_test_network { pub struct ParachainXcmRouter($crate::PhantomData); impl> $crate::SendXcm for ParachainXcmRouter { - fn send_xcm(destination: impl Into<$crate::MultiLocation>, message: $crate::Xcm<()>) -> $crate::SendResult { + type Ticket = ($crate::ParaId, $crate::MultiLocation, $crate::Xcm<()>); + fn validate( + destination: &mut Option<$crate::MultiLocation>, + message: &mut Option<$crate::Xcm<()>>, + ) -> $crate::SendResult<($crate::ParaId, $crate::MultiLocation, $crate::Xcm<()>)> { use $crate::{UmpSink, XcmpMessageHandlerT}; - let destination = destination.into(); - match destination.interior() { - $crate::Junctions::Here if destination.parent_count() == 1 => { - $crate::PARA_MESSAGE_BUS.with( - |b| b.borrow_mut().push_back((T::get(), destination, message))); - Ok(()) - }, + let d = destination.take().ok_or($crate::SendError::MissingArgument)?; + match (d.interior(), d.parent_count()) { + ($crate::Junctions::Here, 1) => {}, $( - $crate::X1($crate::Parachain(id)) if *id == $para_id && destination.parent_count() == 1 => { - $crate::PARA_MESSAGE_BUS.with( - |b| b.borrow_mut().push_back((T::get(), destination, message))); - Ok(()) - }, + ($crate::X1($crate::Parachain(id)), 1) if id == &$para_id => {} )* - _ => Err($crate::SendError::CannotReachDestination(destination, message)), + _ => { + *destination = Some(d); + return Err($crate::SendError::NotApplicable) + }, } + let m = message.take().ok_or($crate::SendError::MissingArgument)?; + Ok(((T::get(), d, m), $crate::MultiAssets::new())) + } + fn deliver( + triple: ($crate::ParaId, $crate::MultiLocation, $crate::Xcm<()>), + ) -> Result<$crate::XcmHash, $crate::SendError> { + let hash = $crate::fake_message_hash(&triple.2); + $crate::PARA_MESSAGE_BUS.with(|b| b.borrow_mut().push_back(triple)); + Ok(hash) } } /// XCM router for relay chain. pub struct RelayChainXcmRouter; impl $crate::SendXcm for RelayChainXcmRouter { - fn send_xcm(destination: impl Into<$crate::MultiLocation>, message: $crate::Xcm<()>) -> $crate::SendResult { + type Ticket = ($crate::MultiLocation, $crate::Xcm<()>); + fn validate( + destination: &mut Option<$crate::MultiLocation>, + message: &mut Option<$crate::Xcm<()>>, + ) -> $crate::SendResult<($crate::MultiLocation, $crate::Xcm<()>)> { use $crate::DmpMessageHandlerT; - let destination = destination.into(); - match destination.interior() { + let d = destination.take().ok_or($crate::SendError::MissingArgument)?; + match (d.interior(), d.parent_count()) { $( - $crate::X1($crate::Parachain(id)) if *id == $para_id && destination.parent_count() == 0 => { - $crate::RELAY_MESSAGE_BUS.with( - |b| b.borrow_mut().push_back((destination, message))); - Ok(()) - }, + ($crate::X1($crate::Parachain(id)), 0) if id == &$para_id => {}, )* - _ => Err($crate::SendError::Unroutable), + _ => { + *destination = Some(d); + return Err($crate::SendError::NotApplicable) + }, } + let m = message.take().ok_or($crate::SendError::MissingArgument)?; + Ok(((d, m), $crate::MultiAssets::new())) + } + fn deliver( + pair: ($crate::MultiLocation, $crate::Xcm<()>), + ) -> Result<$crate::XcmHash, $crate::SendError> { + let hash = $crate::fake_message_hash(&pair.1); + $crate::RELAY_MESSAGE_BUS.with(|b| b.borrow_mut().push_back(pair)); + Ok(hash) } } };