diff --git a/Cargo.lock b/Cargo.lock index 87dc239206fa..07796193f2a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,7 +33,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.5", ] [[package]] @@ -44,7 +44,7 @@ checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.2.1", + "cpufeatures 0.2.2", "opaque-debug 0.3.0", ] @@ -68,7 +68,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.6", "once_cell", "version_check", ] @@ -99,24 +99,24 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.51" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" +checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" [[package]] name = "approx" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] [[package]] name = "arbitrary" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510c76ecefdceada737ea728f4f9a84bd2e1ef29f1ba555e560940fe279954de" +checksum = "c38b6b6b79f671c25e1a3e785b7b82d7562ffc9cd3efdc98627e5668a2472490" [[package]] name = "arrayref" @@ -208,9 +208,9 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.0.2" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" +checksum = "c290043c9a95b05d45e952fb6383c67bcb61471f60cfa21e890dba6654234f43" dependencies = [ "async-channel", "async-executor", @@ -243,9 +243,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" +checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" dependencies = [ "event-listener", ] @@ -298,7 +298,7 @@ dependencies = [ "memchr", "num_cpus", "once_cell", - "pin-project-lite 0.2.7", + "pin-project-lite 0.2.8", "pin-utils", "slab", "wasm-bindgen-futures", @@ -306,9 +306,9 @@ dependencies = [ [[package]] name = "async-std-resolver" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed4e2c3da14d8ad45acb1e3191db7a918e9505b6f155b218e70a7c9a1a48c638" +checksum = "dbf3e776afdf3a2477ef4854b85ba0dff3bd85792f685fb3c68948b4d304e4f0" dependencies = [ "async-std", "async-trait", @@ -320,9 +320,9 @@ dependencies = [ [[package]] name = "async-task" -version = "4.0.3" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" +checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9" [[package]] name = "async-trait" @@ -345,7 +345,7 @@ dependencies = [ "futures-sink", "futures-util", "memchr", - "pin-project-lite 0.2.7", + "pin-project-lite 0.2.8", ] [[package]] @@ -358,7 +358,7 @@ dependencies = [ "futures-sink", "futures-util", "memchr", - "pin-project-lite 0.2.7", + "pin-project-lite 0.2.8", ] [[package]] @@ -389,9 +389,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" @@ -403,7 +403,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "libc", - "miniz_oxide", + "miniz_oxide 0.4.4", "object", "rustc-demangle", ] @@ -434,9 +434,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "base64ct" -version = "1.3.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874f8444adcb4952a8bc51305c8be95c8ec8237bb0d2e78d2e039f771f8828a0" +checksum = "dea908e7347a8c64e378c17e30ef880ad73e3b4498346b055c2c00ea342f3179" [[package]] name = "beef" @@ -525,9 +525,9 @@ dependencies = [ [[package]] name = "bimap" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50ae17cabbc8a38a1e3e4c1a6a664e9a09672dc14d0896fa8d865d3a5a446b07" +checksum = "bc0455254eb5c6964c4545d8bac815e1a1be4f3afe0ae695ea539c12d728d44b" [[package]] name = "bincode" @@ -588,9 +588,9 @@ dependencies = [ [[package]] name = "blake2" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94ba84325db59637ffc528bbe8c7f86c02c57cff5c0e2b9b00f9a851f42f309" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" dependencies = [ "digest 0.10.3", ] @@ -675,16 +675,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "block-padding 0.2.1", - "generic-array 0.14.4", + "generic-array 0.14.5", ] [[package]] name = "block-buffer" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.5", ] [[package]] @@ -704,9 +704,9 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "blocking" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046e47d4b2d391b1f6f8b407b1deb8dee56c1852ccd868becf2710f601b5f427" +checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" dependencies = [ "async-channel", "async-task", @@ -911,15 +911,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.8.0" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" [[package]] name = "byte-slice-cast" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c751592b77c499e7bce34d99d67c2c11bdc0574e9a488ddade14150a4698" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" [[package]] name = "byte-tools" @@ -962,15 +962,15 @@ dependencies = [ [[package]] name = "cache-padded" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" [[package]] name = "camino" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d74260d9bf6944e2208aa46841b4b8f0d7ffc0849a06837b2f510337f86b2b" +checksum = "6f3132262930b0522068049f5870a856ab8affc80c70d08b6ecb785771a6fc23" dependencies = [ "serde", ] @@ -992,16 +992,16 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.4", + "semver 1.0.7", "serde", "serde_json", ] [[package]] name = "cc" -version = "1.0.72" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" dependencies = [ "jobserver", ] @@ -1088,7 +1088,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.5", ] [[package]] @@ -1102,13 +1102,13 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" +checksum = "4cc00842eed744b858222c4c9faf7243aafc6d33f92f96935263ef4d8a41ce21" dependencies = [ "glob", "libc", - "libloading 0.7.2", + "libloading 0.7.3", ] [[package]] @@ -1195,9 +1195,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "core-foundation" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys", "libc", @@ -1211,9 +1211,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpp_demangle" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931ab2a3e6330a07900b8e7ca4e106cdcbb93f2b9a52df55e54ee53d8305b55d" +checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" dependencies = [ "cfg-if 1.0.0", ] @@ -1229,9 +1229,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" dependencies = [ "libc", ] @@ -1327,18 +1327,18 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.3.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "crossbeam-channel" -version = "0.5.1" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -1357,10 +1357,11 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.5" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" dependencies = [ + "autocfg", "cfg-if 1.0.0", "crossbeam-utils", "lazy_static", @@ -1380,9 +1381,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ "cfg-if 1.0.0", "lazy_static", @@ -1400,7 +1401,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.5", "rand_core 0.6.3", "subtle", "zeroize", @@ -1412,7 +1413,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.5", "typenum", ] @@ -1422,7 +1423,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.5", "subtle", ] @@ -1432,7 +1433,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.5", "subtle", ] @@ -1469,9 +1470,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" dependencies = [ "quote", "syn", @@ -1551,9 +1552,9 @@ dependencies = [ [[package]] name = "debugid" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91cf5a8c2f2097e2a32627123508635d47ce10563d999ec1a95addf08b502ba" +checksum = "d6ee87af31d84ef885378aebca32be3d682b0e0dc119d5b4860a2c5bb5046730" dependencies = [ "uuid", ] @@ -1618,7 +1619,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.5", ] [[package]] @@ -1627,7 +1628,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "block-buffer 0.10.0", + "block-buffer 0.10.2", "crypto-common", "subtle", ] @@ -1664,12 +1665,12 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", - "redox_users 0.4.0", + "redox_users 0.4.3", "winapi 0.3.9", ] @@ -1680,7 +1681,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users 0.4.0", + "redox_users 0.4.3", "winapi 0.3.9", ] @@ -1750,9 +1751,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" +checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" [[package]] name = "ecdsa" @@ -1767,9 +1768,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.3.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74e1069e39f1454367eb2de793ed062fac4c35c2934b76a81d90dd9abcd28816" +checksum = "3d5c4b5e5959dc2c2b89918d8e2cc40fcdd623cef026ed09d2f0ee05199dc8e4" dependencies = [ "signature", ] @@ -1784,7 +1785,7 @@ dependencies = [ "ed25519", "rand 0.7.3", "serde", - "sha2 0.9.8", + "sha2 0.9.9", "zeroize", ] @@ -1804,7 +1805,7 @@ dependencies = [ "crypto-bigint", "der", "ff", - "generic-array 0.14.4", + "generic-array 0.14.5", "group", "rand_core 0.6.3", "sec1", @@ -1820,20 +1821,20 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.30" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "enum-as-inner" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" +checksum = "570d109b813e904becc80d8d5da38376818a143348413f7149f1340fe04754d4" dependencies = [ - "heck 0.3.3", + "heck 0.4.0", "proc-macro2", "quote", "syn", @@ -1870,19 +1871,6 @@ dependencies = [ "syn", ] -[[package]] -name = "env_logger" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -dependencies = [ - "atty", - "humantime 1.3.0", - "log", - "regex", - "termcolor", -] - [[package]] name = "env_logger" version = "0.9.0" @@ -1890,7 +1878,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" dependencies = [ "atty", - "humantime 2.1.0", + "humantime", "log", "regex", "termcolor", @@ -1934,9 +1922,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.5.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" +checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" [[package]] name = "exit-future" @@ -1965,7 +1953,7 @@ version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3774182a5df13c3d1690311ad32fbe913feef26baba609fa2dd5f72042bd2ab6" dependencies = [ - "blake2 0.10.2", + "blake2 0.10.4", "fs-err", "proc-macro2", "quote", @@ -1974,9 +1962,9 @@ dependencies = [ [[package]] name = "eyre" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "221239d1d5ea86bf5d6f91c9d6bc3646ffe471b08ff9b0f91c44f115ac969d2b" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" dependencies = [ "indenter", "once_cell", @@ -1996,9 +1984,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.5.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ "instant", ] @@ -2065,11 +2053,11 @@ dependencies = [ [[package]] name = "file-per-thread-logger" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fdbe0d94371f9ce939b555dd342d0686cc4c0cadbcd4b61d70af5ff97eb4126" +checksum = "21e16290574b39ee41c71aeb90ae960c504ebaf1e2a1c87bd52aa56ed6e1a02f" dependencies = [ - "env_logger 0.7.1", + "env_logger", "log", ] @@ -2115,21 +2103,21 @@ dependencies = [ [[package]] name = "fixedbitset" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" +checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" [[package]] name = "flate2" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af" dependencies = [ "cfg-if 1.0.0", "crc32fast", "libc", "libz-sys", - "miniz_oxide", + "miniz_oxide 0.5.1", ] [[package]] @@ -2447,9 +2435,9 @@ dependencies = [ [[package]] name = "fs-err" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ebd3504ad6116843b8375ad70df74e7bfe83cac77a1f3fe73200c844d43bfe0" +checksum = "5bd79fa345a495d3ae89fb7165fec01c0e72f41821d642dda363a1e97975652e" [[package]] name = "fs-swap" @@ -2567,7 +2555,7 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite 0.2.7", + "pin-project-lite 0.2.8", "waker-fn", ] @@ -2625,7 +2613,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.7", + "pin-project-lite 0.2.8", "pin-utils", "slab", ] @@ -2656,9 +2644,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" dependencies = [ "typenum", "version_check", @@ -2679,9 +2667,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if 1.0.0", "libc", @@ -2743,15 +2731,14 @@ dependencies = [ [[package]] name = "gloo-timers" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" dependencies = [ "futures-channel", "futures-core", "js-sys", "wasm-bindgen", - "web-sys", ] [[package]] @@ -2767,9 +2754,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" dependencies = [ "bytes 1.1.0", "fnv", @@ -2780,7 +2767,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.6.9", + "tokio-util 0.7.1", "tracing", ] @@ -2900,7 +2887,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", - "generic-array 0.14.4", + "generic-array 0.14.5", "hmac 0.8.1", ] @@ -2928,13 +2915,13 @@ dependencies = [ [[package]] name = "http" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" dependencies = [ "bytes 1.1.0", "fnv", - "itoa 0.4.8", + "itoa 1.0.1", ] [[package]] @@ -2945,14 +2932,14 @@ checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" dependencies = [ "bytes 1.1.0", "http", - "pin-project-lite 0.2.7", + "pin-project-lite 0.2.8", ] [[package]] name = "httparse" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" +checksum = "6330e8a36bd8c859f3fa6d9382911fbb7147ec39807f63b923933a247240b9ba" [[package]] name = "httpdate" @@ -2960,15 +2947,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" -[[package]] -name = "humantime" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error 1.2.3", -] - [[package]] name = "humantime" version = "2.1.0" @@ -2991,7 +2969,7 @@ dependencies = [ "httparse", "httpdate", "itoa 1.0.1", - "pin-project-lite 0.2.7", + "pin-project-lite 0.2.8", "socket2 0.4.4", "tokio", "tower-service", @@ -3145,9 +3123,9 @@ dependencies = [ [[package]] name = "integer-encoding" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90c11140ffea82edce8dcd74137ce9324ec24b3cf0175fc9d7e29164da9915b8" +checksum = "0e85a1509a128c855368e135cffcde7eac17d8e1083f41e2b98c58bc1a5074be" [[package]] name = "integer-sqrt" @@ -3193,9 +3171,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" +checksum = "35e70ee094dc02fd9c13fdad4940090f22dbd6ac7c9e7094a46cf0232a50bc7c" [[package]] name = "itertools" @@ -3229,9 +3207,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.55" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" dependencies = [ "wasm-bindgen", ] @@ -3391,14 +3369,14 @@ dependencies = [ "jsonrpsee-core", "jsonrpsee-types", "pin-project 1.0.10", - "rustls-native-certs 0.6.1", + "rustls-native-certs 0.6.2", "soketto", "thiserror", "tokio", - "tokio-rustls 0.23.2", + "tokio-rustls 0.23.3", "tokio-util 0.7.1", "tracing", - "webpki-roots 0.22.2", + "webpki-roots 0.22.3", ] [[package]] @@ -3463,9 +3441,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cc5937366afd3b38071f400d1ce5bd8b1d40b5083cc14e6f8dbcc4032a7f5bb" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" dependencies = [ "cfg-if 1.0.0", "ecdsa", @@ -3666,9 +3644,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.121" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd" [[package]] name = "libgit2-sys" @@ -3694,9 +3672,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" dependencies = [ "cfg-if 1.0.0", "winapi 0.3.9", @@ -3704,9 +3682,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" +checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" [[package]] name = "libp2p" @@ -3751,9 +3729,9 @@ dependencies = [ [[package]] name = "libp2p-core" -version = "0.30.0" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef22d9bba1e8bcb7ec300073e6802943fe8abb8190431842262b5f1c30abba1" +checksum = "86aad7d54df283db817becded03e611137698a6509d4237a96881976a162340c" dependencies = [ "asn1_der", "bs58", @@ -3762,6 +3740,7 @@ dependencies = [ "fnv", "futures 0.3.21", "futures-timer", + "instant", "lazy_static", "libsecp256k1", "log", @@ -3775,7 +3754,7 @@ dependencies = [ "rand 0.8.5", "ring", "rw-stream-sink", - "sha2 0.9.8", + "sha2 0.9.9", "smallvec", "thiserror", "unsigned-varint 0.7.1", @@ -3846,7 +3825,7 @@ dependencies = [ "prost-build", "rand 0.7.3", "regex", - "sha2 0.9.8", + "sha2 0.9.9", "smallvec", "unsigned-varint 0.7.1", "wasm-timer", @@ -3887,7 +3866,7 @@ dependencies = [ "prost", "prost-build", "rand 0.7.3", - "sha2 0.9.8", + "sha2 0.9.9", "smallvec", "uint", "unsigned-varint 0.7.1", @@ -3963,7 +3942,7 @@ dependencies = [ "prost", "prost-build", "rand 0.8.5", - "sha2 0.9.8", + "sha2 0.9.9", "snow", "static_assertions", "x25519-dalek", @@ -4054,7 +4033,7 @@ dependencies = [ "prost", "prost-build", "rand 0.8.5", - "sha2 0.9.8", + "sha2 0.9.9", "thiserror", "unsigned-varint 0.7.1", "void", @@ -4210,7 +4189,7 @@ dependencies = [ "libsecp256k1-gen-genmult", "rand 0.8.5", "serde", - "sha2 0.9.8", + "sha2 0.9.9", "typenum", ] @@ -4245,9 +4224,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" +checksum = "6f35facd4a5673cb5a48822be2be1d4236c1c99cb4113cab7061ac720d5bf859" dependencies = [ "cc", "libc", @@ -4288,10 +4267,11 @@ checksum = "5284f00d480e1c39af34e72f8ad60b94f47007e3481cd3b731c1d67190ddc7b7" [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ + "autocfg", "scopeguard", ] @@ -4335,9 +4315,9 @@ dependencies = [ [[package]] name = "lz4" -version = "1.23.2" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac20ed6991e01bf6a2e68cc73df2b389707403662a8ba89f68511fb340f724c" +checksum = "4edcb94251b1c375c459e5abe9fb0168c1c826c3370172684844f8f3f8d1a885" dependencies = [ "libc", "lz4-sys", @@ -4345,9 +4325,9 @@ dependencies = [ [[package]] name = "lz4-sys" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca79aa95d8b3226213ad454d328369853be3a1382d89532a854f4d69640acae" +checksum = "d7be8908e2ed6f31c02db8a9fa962f03e36c53fbfde437363eae3306b85d7e17" dependencies = [ "cc", "libc", @@ -4434,18 +4414,18 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.5.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e" +checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" dependencies = [ "libc", ] [[package]] name = "memoffset" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] @@ -4496,7 +4476,7 @@ dependencies = [ "coarsetime", "crossbeam-queue", "derive_more", - "env_logger 0.9.0", + "env_logger", "futures 0.3.21", "futures-timer", "log", @@ -4539,6 +4519,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.6.23" @@ -4560,14 +4549,15 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" +checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" dependencies = [ "libc", "log", "miow 0.3.7", "ntapi", + "wasi 0.11.0+wasi-snapshot-preview1", "winapi 0.3.9", ] @@ -4649,9 +4639,9 @@ dependencies = [ "blake2s_simd", "blake3 0.3.8", "digest 0.9.0", - "generic-array 0.14.4", + "generic-array 0.14.5", "multihash-derive", - "sha2 0.9.8", + "sha2 0.9.9", "sha3 0.9.1", "unsigned-varint 0.5.1", ] @@ -4663,9 +4653,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "752a61cd890ff691b4411423d23816d5866dd5621e4d1c5687a53b94b5a979d8" dependencies = [ "digest 0.9.0", - "generic-array 0.14.4", + "generic-array 0.14.5", "multihash-derive", - "sha2 0.9.8", + "sha2 0.9.9", "unsigned-varint 0.7.1", ] @@ -4749,9 +4739,9 @@ checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" [[package]] name = "native-tls" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" dependencies = [ "lazy_static", "libc", @@ -4803,20 +4793,19 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "nom" -version = "7.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" dependencies = [ "memchr", "minimal-lexical", - "version_check", ] [[package]] name = "ntapi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" dependencies = [ "winapi 0.3.9", ] @@ -4896,9 +4885,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", @@ -4972,9 +4961,9 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" @@ -5009,9 +4998,9 @@ dependencies = [ [[package]] name = "output_vt100" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" dependencies = [ "winapi 0.3.9", ] @@ -5027,9 +5016,9 @@ dependencies = [ [[package]] name = "owo-colors" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20448fd678ec04e6ea15bbe0476874af65e98a01515d667aa49f1434dc44ebf4" +checksum = "5e72e30578e0d0993c8ae20823dd9cff2bc5517d2f586a8aef462a581e8a03eb" [[package]] name = "pallet-assets" @@ -5863,6 +5852,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-uniques" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#1e0807fb477b21edd373eb88778336d5ff23d3a7" +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" @@ -5943,9 +5947,9 @@ dependencies = [ [[package]] name = "parity-db" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d121a9af17a43efd0a38c6afa508b927ba07785bd4709efb2ac03bf77efef8d" +checksum = "b3e7f385d61562f5834282b90aa50b41f38a35cf64d5209b8b05487b50553dbe" dependencies = [ "blake2-rfc", "crc32fast", @@ -6092,7 +6096,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" dependencies = [ "lock_api", - "parking_lot_core 0.9.1", + "parking_lot_core 0.9.2", ] [[package]] @@ -6104,20 +6108,20 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.2.10", + "redox_syscall 0.2.13", "smallvec", "winapi 0.3.9", ] [[package]] name = "parking_lot_core" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.10", + "redox_syscall 0.2.13", "smallvec", "windows-sys", ] @@ -6265,9 +6269,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" [[package]] name = "pin-utils" @@ -6288,9 +6292,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.22" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "platforms" @@ -6318,7 +6322,7 @@ name = "polkadot-approval-distribution" version = "0.9.19" dependencies = [ "assert_matches", - "env_logger 0.9.0", + "env_logger", "futures 0.3.21", "log", "polkadot-node-network-protocol", @@ -6339,7 +6343,7 @@ version = "0.9.19" dependencies = [ "assert_matches", "bitvec", - "env_logger 0.9.0", + "env_logger", "futures 0.3.21", "log", "maplit", @@ -6389,7 +6393,7 @@ name = "polkadot-availability-recovery" version = "0.9.19" dependencies = [ "assert_matches", - "env_logger 0.9.0", + "env_logger", "fatality", "futures 0.3.21", "futures-timer", @@ -6485,7 +6489,7 @@ version = "0.9.19" dependencies = [ "always-assert", "assert_matches", - "env_logger 0.9.0", + "env_logger", "fatality", "futures 0.3.21", "futures-timer", @@ -6676,7 +6680,7 @@ version = "0.9.19" dependencies = [ "assert_matches", "bitvec", - "env_logger 0.9.0", + "env_logger", "futures 0.3.21", "futures-timer", "kvdb", @@ -7075,7 +7079,7 @@ dependencies = [ "assert_matches", "async-trait", "derive_more", - "env_logger 0.9.0", + "env_logger", "fatality", "futures 0.3.21", "itertools", @@ -7183,7 +7187,7 @@ dependencies = [ name = "polkadot-performance-test" version = "0.9.19" dependencies = [ - "env_logger 0.9.0", + "env_logger", "kusama-runtime", "log", "polkadot-erasure-coding", @@ -7488,7 +7492,7 @@ dependencies = [ "async-trait", "beefy-gadget", "beefy-primitives", - "env_logger 0.9.0", + "env_logger", "frame-system-rpc-runtime-api", "futures 0.3.21", "hex-literal", @@ -7832,7 +7836,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ - "cpufeatures 0.2.1", + "cpufeatures 0.2.2", "opaque-debug 0.3.0", "universal-hash", ] @@ -7844,7 +7848,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures 0.2.2", "opaque-debug 0.3.0", "universal-hash", ] @@ -7871,15 +7875,15 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "predicates" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95e5a7689e456ab905c22c2b48225bb921aba7c8dfa58440d68ba13f6222a715" +checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" dependencies = [ "difflib", "itertools", @@ -7888,15 +7892,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" +checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" [[package]] name = "predicates-tree" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "338c7be2905b732ae3984a2f40032b5e94fd8f52505b186c7d4d68d193445df7" +checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" dependencies = [ "predicates-core", "termtree", @@ -7904,9 +7908,9 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.0.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc" +checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563" dependencies = [ "ansi_term", "ctor", @@ -8073,9 +8077,9 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.16" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd136ff4382c4753fc061cb9e4712ab2af263376b95bbd5bd8cd50c020b78e69" +checksum = "871372391786ccec00d3c5d3d6608905b3d4db263639cfe075d3b60a736d115a" dependencies = [ "cc", ] @@ -8191,14 +8195,14 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.6", ] [[package]] name = "rand_distr" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964d548f8e7d12e102ef183a0de7e98180c9f8729f555897a857b96e48122d2f" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", "rand 0.8.5", @@ -8239,9 +8243,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +checksum = "fd249e82c21598a9a426a4e00dd7adc1d640b22445ec8545feef801d1a74c221" dependencies = [ "autocfg", "crossbeam-deque", @@ -8251,14 +8255,13 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +checksum = "9f51245e1e62e1f1629cbfec37b5793bbabcaeb90f30e94d2ba03564687353e4" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "lazy_static", "num_cpus", ] @@ -8270,9 +8273,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ "bitflags", ] @@ -8290,12 +8293,13 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.3", - "redox_syscall 0.2.10", + "getrandom 0.2.6", + "redox_syscall 0.2.13", + "thiserror", ] [[package]] @@ -8404,7 +8408,7 @@ name = "remote-externalities" version = "0.10.0-dev" source = "git+https://github.com/paritytech/substrate?branch=master#1e0807fb477b21edd373eb88778336d5ff23d3a7" dependencies = [ - "env_logger 0.9.0", + "env_logger", "jsonrpsee", "log", "parity-scale-codec", @@ -8448,7 +8452,7 @@ dependencies = [ "mime", "native-tls", "percent-encoding 2.1.0", - "pin-project-lite 0.2.7", + "pin-project-lite 0.2.8", "serde", "serde_json", "serde_urlencoded", @@ -8473,9 +8477,9 @@ dependencies = [ [[package]] name = "retain_mut" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "448296241d034b96c11173591deaa1302f2c17b56092106c1f92c1bc0183a8c9" +checksum = "8c31b5c4033f8fdde8700e4657be2c497e7288f01515be52168c631e2e4d4086" [[package]] name = "ring" @@ -8644,14 +8648,14 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.4", + "semver 1.0.7", ] [[package]] name = "rustix" -version = "0.33.5" +version = "0.33.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03627528abcc4a365554d32a9f3bbf67f7694c102cfeda792dc86a2d6057cc85" +checksum = "7ac32d9fc97153ca55305284cb6c2dcbb84e1fc6d7ac13392cea02222f2d8741" dependencies = [ "bitflags", "errno", @@ -8676,9 +8680,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.2" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" +checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921" dependencies = [ "log", "ring", @@ -8700,9 +8704,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca9ebdfa27d3fc180e42879037b5338ab1c040c06affd00d8338598e7800943" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -8712,9 +8716,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "0.2.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" dependencies = [ "base64", ] @@ -8738,9 +8742,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "salsa20" @@ -8843,7 +8847,7 @@ version = "4.0.0-dev" source = "git+https://github.com/paritytech/substrate?branch=master#1e0807fb477b21edd373eb88778336d5ff23d3a7" dependencies = [ "impl-trait-for-tuples", - "memmap2 0.5.0", + "memmap2 0.5.3", "parity-scale-codec", "sc-chain-spec-derive", "sc-network", @@ -9785,7 +9789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ "der", - "generic-array 0.14.4", + "generic-array 0.14.5", "pkcs8", "subtle", "zeroize", @@ -9820,9 +9824,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.4.2" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" dependencies = [ "bitflags", "core-foundation", @@ -9833,9 +9837,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.4.2" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ "core-foundation-sys", "libc", @@ -9861,9 +9865,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.4" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" +checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4" dependencies = [ "serde", ] @@ -9970,7 +9974,7 @@ checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures 0.2.2", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -9982,7 +9986,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures 0.2.2", "digest 0.10.3", ] @@ -10000,25 +10004,25 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures 0.2.2", "digest 0.9.0", "opaque-debug 0.3.0", ] [[package]] name = "sha2" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures 0.2.2", "digest 0.10.3", ] @@ -10036,9 +10040,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f935e31cf406e8c0e96c2815a5516181b7004ae8c5f296293221e9b1e356bd" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" dependencies = [ "digest 0.10.3", "keccak", @@ -10061,9 +10065,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1" +checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d" dependencies = [ "libc", "signal-hook-registry", @@ -10101,9 +10105,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] name = "slot-range-helper" @@ -10150,7 +10154,7 @@ dependencies = [ "rand_core 0.6.3", "ring", "rustc_version 0.3.3", - "sha2 0.9.8", + "sha2 0.9.9", "subtle", "x25519-dalek", ] @@ -10214,7 +10218,7 @@ name = "sp-api-proc-macro" version = "4.0.0-dev" source = "git+https://github.com/paritytech/substrate?branch=master#1e0807fb477b21edd373eb88778336d5ff23d3a7" dependencies = [ - "blake2 0.10.2", + "blake2 0.10.4", "proc-macro-crate 1.1.3", "proc-macro2", "quote", @@ -10423,11 +10427,11 @@ name = "sp-core-hashing" version = "4.0.0" source = "git+https://github.com/paritytech/substrate?branch=master#1e0807fb477b21edd373eb88778336d5ff23d3a7" dependencies = [ - "blake2 0.10.2", + "blake2 0.10.4", "byteorder", "digest 0.10.3", - "sha2 0.10.1", - "sha3 0.10.0", + "sha2 0.10.2", + "sha3 0.10.1", "sp-std", "twox-hash", ] @@ -10892,11 +10896,12 @@ dependencies = [ [[package]] name = "ss58-registry" -version = "1.12.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8319f44e20b42e5c11b88b1ad4130c35fe2974665a007b08b02322070177136a" +checksum = "7b84a70894df7a73666e0694f44b41a9571625e9546fb58a0818a565d2c7e084" dependencies = [ "Inflector", + "num-format", "proc-macro2", "quote", "serde", @@ -10943,7 +10948,7 @@ dependencies = [ "sub-tokens", "thiserror", "tokio", - "tracing-subscriber 0.3.10", + "tracing-subscriber 0.3.11", "westend-runtime", ] @@ -11058,7 +11063,7 @@ dependencies = [ "hmac 0.11.0", "pbkdf2 0.8.0", "schnorrkel", - "sha2 0.9.8", + "sha2 0.9.9", "zeroize", ] @@ -11208,21 +11213,21 @@ dependencies = [ [[package]] name = "symbolic-common" -version = "8.6.0" +version = "8.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e92a52f07eed9afba3d6f883652cde7cd75fcf327dd44e84f210958379158737" +checksum = "ac6aac7b803adc9ee75344af7681969f76d4b38e4723c6eaacf3b28f5f1d87ff" dependencies = [ "debugid", - "memmap2 0.5.0", + "memmap2 0.5.3", "stable_deref_trait", "uuid", ] [[package]] name = "symbolic-demangle" -version = "8.6.0" +version = "8.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9abc81544d9964975269165bfe5ad198d8b9e2e809c46527323f95588a57693" +checksum = "8143ea5aa546f86c64f9b9aafdd14223ffad4ecd2d58575c63c21335909c99a7" dependencies = [ "cpp_demangle", "rustc-demangle", @@ -11260,20 +11265,20 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff" +checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" [[package]] name = "tempfile" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if 1.0.0", + "fastrand", "libc", - "rand 0.8.5", - "redox_syscall 0.2.10", + "redox_syscall 0.2.13", "remove_dir_all", "winapi 0.3.9", ] @@ -11291,18 +11296,18 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] [[package]] name = "termtree" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13a4ec180a2de59b57434704ccfad967f789b12737738798fa08798cd5824c16" +checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "test-parachain-adder" @@ -11488,9 +11493,9 @@ dependencies = [ [[package]] name = "tikv-jemalloc-sys" -version = "0.4.2+5.2.1-patched.2" +version = "0.4.3+5.2.1-patched.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5844e429d797c62945a566f8da4e24c7fe3fbd5d6617fd8bf7a0b7dc1ee0f22e" +checksum = "a1792ccb507d955b46af42c123ea8863668fae24d03721e40cad6a41773dbb49" dependencies = [ "cc", "fs_extra", @@ -11499,9 +11504,9 @@ dependencies = [ [[package]] name = "tikv-jemallocator" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c14a5a604eb8715bc5785018a37d00739b180bcf609916ddf4393d33d49ccdf" +checksum = "a5b7bcecfafe4998587d636f9ae9d55eb9d0499877b88757767c346875067098" dependencies = [ "libc", "tikv-jemalloc-sys", @@ -11530,7 +11535,7 @@ dependencies = [ "pbkdf2 0.4.0", "rand 0.7.3", "rustc-hash", - "sha2 0.9.8", + "sha2 0.9.9", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -11570,11 +11575,11 @@ dependencies = [ "bytes 1.1.0", "libc", "memchr", - "mio 0.8.0", + "mio 0.8.2", "num_cpus", "once_cell", "parking_lot 0.12.0", - "pin-project-lite 0.2.7", + "pin-project-lite 0.2.8", "signal-hook-registry", "socket2 0.4.4", "tokio-macros", @@ -11615,11 +11620,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.2" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" +checksum = "4151fda0cf2798550ad0b34bcfc9b9dcc2a9d2471c895c68f3a8818e54f2389e" dependencies = [ - "rustls 0.20.2", + "rustls 0.20.4", "tokio", "webpki 0.22.0", ] @@ -11631,7 +11636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", - "pin-project-lite 0.2.7", + "pin-project-lite 0.2.8", "tokio", ] @@ -11657,7 +11662,7 @@ dependencies = [ "futures-core", "futures-sink", "log", - "pin-project-lite 0.2.7", + "pin-project-lite 0.2.8", "tokio", ] @@ -11671,15 +11676,16 @@ dependencies = [ "futures-core", "futures-io", "futures-sink", - "pin-project-lite 0.2.7", + "pin-project-lite 0.2.8", "tokio", + "tracing", ] [[package]] name = "toml" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] @@ -11692,13 +11698,13 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80b9fa4360528139bc96100c160b7ae879f5567f49f1782b0b02035b0358ebf3" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" dependencies = [ "cfg-if 1.0.0", "log", - "pin-project-lite 0.2.7", + "pin-project-lite 0.2.8", "tracing-attributes", "tracing-core", ] @@ -11716,9 +11722,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.22" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" dependencies = [ "lazy_static", "valuable", @@ -11769,9 +11775,9 @@ dependencies = [ [[package]] name = "tracing-serde" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" dependencies = [ "serde", "tracing-core", @@ -11802,9 +11808,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9df98b037d039d03400d9dd06b0f8ce05486b5f25e9a2d7d36196e142ebbc52" +checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" dependencies = [ "ansi_term", "lazy_static", @@ -11842,9 +11848,9 @@ dependencies = [ [[package]] name = "trust-dns-proto" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0d7f5db438199a6e2609debe3f69f808d074e0a2888ee0bccb45fe234d03f4" +checksum = "ca94d4e9feb6a181c690c4040d7a24ef34018d8313ac5044a61d21222ae24e31" dependencies = [ "async-trait", "cfg-if 1.0.0", @@ -11866,9 +11872,9 @@ dependencies = [ [[package]] name = "trust-dns-resolver" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ad17b608a64bd0735e67bde16b0636f8aa8591f831a25d18443ed00a699770" +checksum = "ecae383baad9995efaa34ce8e57d12c3f305e545887472a492b838f4b5cfb77a" dependencies = [ "cfg-if 1.0.0", "futures-util", @@ -11916,14 +11922,15 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.53" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d664de8ea7e531ad4c0f5a834f20b8cb2b8e6dfe88d05796ee7887518ed67b9" +checksum = "606ab3fe0065741fdbb51f64bcb6ba76f13fad49f1723030041826c631782764" dependencies = [ "dissimilar", "glob", - "lazy_static", + "once_cell", "serde", + "serde_derive", "serde_json", "termcolor", "toml", @@ -11968,9 +11975,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" @@ -11980,9 +11987,9 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "uint" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" dependencies = [ "byteorder", "crunchy", @@ -12016,9 +12023,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-width" @@ -12038,7 +12045,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.5", "subtle", ] @@ -12141,9 +12148,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "void" @@ -12207,9 +12214,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" dependencies = [ "cfg-if 1.0.0", "serde", @@ -12219,9 +12226,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" dependencies = [ "bumpalo", "lazy_static", @@ -12234,9 +12241,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.28" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -12246,9 +12253,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.78" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -12256,9 +12263,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" dependencies = [ "proc-macro2", "quote", @@ -12269,9 +12276,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" [[package]] name = "wasm-gc-api" @@ -12383,7 +12390,7 @@ dependencies = [ "log", "rustix", "serde", - "sha2 0.9.8", + "sha2 0.9.9", "toml", "winapi 0.3.9", "zstd", @@ -12507,9 +12514,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" dependencies = [ "js-sys", "wasm-bindgen", @@ -12546,9 +12553,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449" +checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" dependencies = [ "webpki 0.22.0", ] @@ -12665,9 +12672,9 @@ dependencies = [ [[package]] name = "which" -version = "4.2.2" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" dependencies = [ "either", "lazy_static", @@ -12725,9 +12732,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" dependencies = [ "windows_aarch64_msvc", "windows_i686_gnu", @@ -12738,33 +12745,33 @@ dependencies = [ [[package]] name = "windows_aarch64_msvc" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" +checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" [[package]] name = "windows_i686_gnu" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" +checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" [[package]] name = "windows_i686_msvc" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" +checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" [[package]] name = "windows_x86_64_gnu" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" +checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" [[package]] name = "windows_x86_64_msvc" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" [[package]] name = "winreg" @@ -12823,6 +12830,8 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", + "serde", + "sp-io", "xcm-procedural", ] @@ -12830,8 +12839,10 @@ dependencies = [ name = "xcm-builder" version = "0.9.19" dependencies = [ + "assert_matches", "frame-support", "frame-system", + "impl-trait-for-tuples", "log", "pallet-balances", "pallet-transaction-payment", @@ -12918,7 +12929,9 @@ version = "0.9.19" dependencies = [ "frame-support", "frame-system", + "log", "pallet-balances", + "pallet-uniques", "pallet-xcm", "parity-scale-codec", "polkadot-core-primitives", @@ -12929,6 +12942,7 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", + "sp-tracing", "xcm", "xcm-builder", "xcm-executor", diff --git a/bridges/bin/rialto-parachain/runtime/src/lib.rs b/bridges/bin/rialto-parachain/runtime/src/lib.rs index 4f81927f0f6f..4e4c7d3cf212 100644 --- a/bridges/bin/rialto-parachain/runtime/src/lib.rs +++ b/bridges/bin/rialto-parachain/runtime/src/lib.rs @@ -69,7 +69,7 @@ use polkadot_parachain::primitives::Sibling; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, - EnsureXcmOrigin, FixedWeightBounds, IsConcrete, LocationInverter, NativeAsset, + EnsureXcmOrigin, FixedWeightBounds, IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, @@ -289,7 +289,7 @@ parameter_types! { pub const RelayLocation: MultiLocation = MultiLocation::parent(); pub const RelayNetwork: NetworkId = NetworkId::Polkadot; pub RelayOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); - pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); + pub UniversalLocation: InteriorMultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); } /// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used @@ -374,12 +374,14 @@ impl Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = NativeAsset; type IsTeleporter = NativeAsset; // <- should be enough to allow teleportation of UNIT - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = UsingComponents, RelayLocation, AccountId, Balances, ()>; type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; + type AssetLocker = (); + type AssetExchanger = (); type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; } @@ -406,7 +408,7 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; diff --git a/bridges/primitives/chain-rococo/src/lib.rs b/bridges/primitives/chain-rococo/src/lib.rs index 127e75d5f8b2..ab724a30465b 100644 --- a/bridges/primitives/chain-rococo/src/lib.rs +++ b/bridges/primitives/chain-rococo/src/lib.rs @@ -43,7 +43,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: sp_version::create_runtime_str!("rococo"), impl_name: sp_version::create_runtime_str!("parity-rococo-v2.0"), authoring_version: 0, - spec_version: 9180, + spec_version: 9190, impl_version: 0, apis: sp_version::create_apis_vec![[]], transaction_version: 0, diff --git a/node/core/pvf/src/host.rs b/node/core/pvf/src/host.rs index 464f8d322648..36fd30d73fc1 100644 --- a/node/core/pvf/src/host.rs +++ b/node/core/pvf/src/host.rs @@ -328,7 +328,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 8b55cdbc0f15..5d3d3c7a6416 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/runtime/common/src/xcm_sender.rs b/runtime/common/src/xcm_sender.rs index 2d75edfd4571..bc806d67f2e0 100644 --- a/runtime/common/src/xcm_sender.rs +++ b/runtime/common/src/xcm_sender.rs @@ -16,34 +16,71 @@ //! 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::*; + +pub trait PriceForParachainDelivery { + fn price_for_parachain_delivery(para: ParaId, message: &Xcm<()>) -> MultiAssets; +} +impl PriceForParachainDelivery for () { + fn price_for_parachain_delivery(_: ParaId, _: &Xcm<()>) -> MultiAssets { + MultiAssets::new() + } +} + +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 0ddf10b72f12..505adef83940 100644 --- a/runtime/kusama/src/lib.rs +++ b/runtime/kusama/src/lib.rs @@ -1988,7 +1988,7 @@ 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, KsmLocation, SovereignAccountOf, Statemine, XcmConfig}; + use xcm_config::{CheckAccount, SovereignAccountOf, Statemine, TokenLocation, XcmConfig}; impl pallet_session_benchmarking::Config for Runtime {} impl pallet_offences_benchmarking::Config for Runtime {} @@ -2002,10 +2002,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() } @@ -2014,11 +2014,7 @@ 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()) }, )); } @@ -2027,11 +2023,10 @@ sp_api::impl_runtime_apis! { type CheckedAccount = CheckAccount; type TrustedTeleporter = TrustedTeleporter; - type TrustedReserve = TrustedReserve; fn get_multi_asset() -> MultiAsset { MultiAsset { - id: Concrete(KsmLocation::get()), + id: Concrete(TokenLocation::get()), fun: Fungible(1 * UNITS), } } @@ -2044,6 +2039,16 @@ sp_api::impl_runtime_apis! { (0u64, Response::Version(Default::default())) } + 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() -> Result { Ok(Statemine::get()) } @@ -2054,10 +2059,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/xcm/mod.rs b/runtime/kusama/src/weights/xcm/mod.rs index f266e076636f..0084315d7b52 100644 --- a/runtime/kusama/src/weights/xcm/mod.rs +++ b/runtime/kusama/src/weights/xcm/mod.rs @@ -75,7 +75,12 @@ impl XcmWeightInfo for KusamaXcmWeight { 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) -> Weight { + fn query_response( + _query_id: &u64, + _response: &Response, + _max_weight: &u64, + _querier: &Option, + ) -> Weight { XcmGeneric::::query_response() } fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { @@ -117,31 +122,23 @@ impl XcmWeightInfo for KusamaXcmWeight { fn descend_origin(_who: &InteriorMultiLocation) -> Weight { XcmGeneric::::descend_origin() } - fn report_error( - _query_id: &QueryId, - _dest: &MultiLocation, - _max_response_weight: &u64, - ) -> Weight { + 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, - ) -> Weight { + 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<()>, ) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets) -> Weight { - Weight::MAX // 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, @@ -157,13 +154,8 @@ impl XcmWeightInfo for KusamaXcmWeight { ) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::initiate_teleport()) } - fn query_holding( - _query_id: &u64, - _dest: &MultiLocation, - _assets: &MultiAssetFilter, - _max_response_weight: &u64, - ) -> Weight { - XcmGeneric::::query_holding() + fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + XcmGeneric::::report_holding() } fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { XcmGeneric::::buy_execution() @@ -192,4 +184,70 @@ impl XcmWeightInfo for KusamaXcmWeight { 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 lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Kusama does not currently support asset locking operations + Weight::MAX + } + fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Kusama does not currently support asset locking operations + Weight::MAX + } + fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Kusama does not currently support asset locking operations + Weight::MAX + } + fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Kusama does not currently support asset locking operations + Weight::MAX + } + fn set_fees_mode(_: &bool) -> Weight { + XcmGeneric::::set_fees_mode() + } + fn set_topic(_topic: &[u8; 32]) -> Weight { + XcmGeneric::::set_topic() + } + fn clear_topic() -> Weight { + XcmGeneric::::clear_topic() + } + fn alias_origin(_: &MultiLocation) -> Weight { + // XCM Executor does not currently support alias origin operations + Weight::MAX + } } 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 b3d256cbd880..21cf6088b319 100644 --- a/runtime/kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/runtime/kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -1,4 +1,4 @@ -// Copyright 2022 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,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,132 @@ 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 { - (21_822_000 as Weight) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) + pub(crate) fn report_holding() -> Weight { + (25_878_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } pub(crate) fn buy_execution() -> Weight { - (3_109_000 as Weight) + (3_697_000 as Weight) } // Storage: XcmPallet Queries (r:1 w:0) pub(crate) fn query_response() -> Weight { - (12_087_000 as Weight) + (13_458_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } pub(crate) fn transact() -> Weight { - (12_398_000 as Weight) + (13_597_000 as Weight) } pub(crate) fn refund_surplus() -> Weight { - (3_247_000 as Weight) + (3_839_000 as Weight) } pub(crate) fn set_error_handler() -> Weight { - (3_086_000 as Weight) + (3_657_000 as Weight) } pub(crate) fn set_appendix() -> Weight { - (3_112_000 as Weight) + (3_757_000 as Weight) } pub(crate) fn clear_error() -> Weight { - (3_118_000 as Weight) + (3_651_000 as Weight) } pub(crate) fn descend_origin() -> Weight { - (4_054_000 as Weight) + (4_589_000 as Weight) } pub(crate) fn clear_origin() -> Weight { - (3_111_000 as Weight) + (3_661_000 as Weight) } // 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 { - (18_425_000 as Weight) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) + (21_351_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: XcmPallet AssetTraps (r:1 w:1) pub(crate) fn claim_asset() -> Weight { - (7_144_000 as Weight) + (7_674_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } pub(crate) fn trap() -> Weight { - (3_060_000 as Weight) + (3_606_000 as Weight) } // 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 { - (21_642_000 as Weight) - .saturating_add(T::DbWeight::get().reads(7 as Weight)) + (30_453_000 as Weight) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: XcmPallet VersionNotifyTargets (r:0 w:1) pub(crate) fn unsubscribe_version() -> Weight { - (4_873_000 as Weight) + (5_543_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // 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 { - (22_809_000 as Weight) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) + (25_464_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } + pub(crate) fn burn_asset() -> Weight { + (5_194_000 as Weight) + } + pub(crate) fn expect_asset() -> Weight { + (3_698_000 as Weight) + } + pub(crate) fn expect_origin() -> Weight { + (3_786_000 as Weight) + } + pub(crate) fn expect_error() -> Weight { + (3_645_000 as Weight) + } + // 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 { + (22_993_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + pub(crate) fn expect_pallet() -> Weight { + (4_043_000 as Weight) + } + // 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 { + (21_668_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + pub(crate) fn clear_transact_status() -> Weight { + (3_673_000 as Weight) + } + pub(crate) fn set_topic() -> Weight { + (3_661_000 as Weight) + } + pub(crate) fn clear_topic() -> Weight { + (3_647_000 as Weight) + } + pub(crate) fn set_fees_mode() -> Weight { + (3_599_000 as Weight) + } } diff --git a/runtime/kusama/src/xcm_config.rs b/runtime/kusama/src/xcm_config.rs index 5363541e31c1..e273bc36e638 100644 --- a/runtime/kusama/src/xcm_config.rs +++ b/runtime/kusama/src/xcm_config.rs @@ -17,10 +17,14 @@ //! XCM configurations for the Kusama runtime. use super::{ - parachains_origin, AccountId, Balances, Call, CouncilCollective, Event, Origin, ParaId, - Runtime, WeightToFee, XcmPallet, + parachains_origin, AccountId, AllPalletsWithSystem, Balances, Call, CouncilCollective, Event, + Origin, ParaId, Runtime, WeightToFee, XcmPallet, +}; +use frame_support::{ + match_types, parameter_types, + traits::{Everything, Nothing}, + weights::Weight, }; -use frame_support::{match_types, parameter_types, traits::Everything, weights::Weight}; use runtime_common::{xcm_sender, ToAuthor}; use xcm::latest::prelude::*; use xcm_builder::{ @@ -28,20 +32,21 @@ use xcm_builder::{ AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, BackingToPlurality, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedWeightBounds, IsChildSystemParachain, IsConcrete, - LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, - TakeWeightCredit, UsingComponents, WeightInfoBounds, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + UsingComponents, WeightInfoBounds, }; 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(); } @@ -52,18 +57,18 @@ 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): @@ -72,14 +77,14 @@ pub type LocalAssetTransactor = XcmCurrencyAdapter< CheckAccount, >; -/// 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, ); @@ -96,17 +101,18 @@ 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 KusamaForStatemine: (MultiAssetFilter, MultiLocation) = (Kusama::get(), Statemine::get()); - pub const KusamaForEncointer: (MultiAssetFilter, MultiLocation) = (Kusama::get(), Parachain(1001).into()); + pub const Ksm: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete(TokenLocation::get()) }); + pub const Statemine: MultiLocation = Parachain(1000).into_location(); + pub const KsmForStatemine: (MultiAssetFilter, MultiLocation) = (Ksm::get(), Statemine::get()); + pub const KsmForEncointer: (MultiAssetFilter, MultiLocation) = (Ksm::get(), Parachain(1001).into_location()); + 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 = { @@ -136,16 +142,25 @@ 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, Call, 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; } parameter_types! { @@ -163,8 +178,9 @@ pub type LocalOriginToLocation = ( CouncilBodyId, >, // And a usual Signed origin to be used in XCM as a corresponding AccountId32 - SignedToAccountId32, + SignedToAccountId32, ); + impl pallet_xcm::Config for Runtime { type Event = Event; // We don't allow any messages to be sent via the transaction yet. This is basically safe to @@ -183,9 +199,14 @@ impl pallet_xcm::Config for Runtime { // transfer. type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; 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>; } diff --git a/runtime/parachains/src/disputes.rs b/runtime/parachains/src/disputes.rs index 29fa811cc44c..4650a02cb28d 100644 --- a/runtime/parachains/src/disputes.rs +++ b/runtime/parachains/src/disputes.rs @@ -1062,7 +1062,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 9e2210f83856..a2030a2692fa 100644 --- a/runtime/parachains/src/dmp.rs +++ b/runtime/parachains/src/dmp.rs @@ -137,6 +137,21 @@ 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) + } + Ok(()) + } + /// Enqueue a downward message to a specific recipient para. /// /// When encoded, the message should not exceed the `config.max_downward_message_size`. diff --git a/runtime/parachains/src/paras/mod.rs b/runtime/parachains/src/paras/mod.rs index 3c5b82479395..c5b932ad7a53 100644 --- a/runtime/parachains/src/paras/mod.rs +++ b/runtime/parachains/src/paras/mod.rs @@ -158,7 +158,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. diff --git a/runtime/parachains/src/ump.rs b/runtime/parachains/src/ump.rs index 7d40a99e9885..47453c89c3ab 100644 --- a/runtime/parachains/src/ump.rs +++ b/runtime/parachains/src/ump.rs @@ -131,7 +131,7 @@ impl, C: Config> UmpSink for XcmSi }, Ok((Ok(xcm_message), weight_used)) => { let xcm_junction = Junction::Parachain(origin.into()); - let outcome = XcmExecutor::execute_xcm(xcm_junction, xcm_message, max_weight); + let outcome = XcmExecutor::execute_xcm(xcm_junction, xcm_message, id, max_weight); match outcome { Outcome::Error(XcmError::WeightLimitReached(required)) => Err((id, required)), outcome => { diff --git a/runtime/parachains/src/ump/benchmarking.rs b/runtime/parachains/src/ump/benchmarking.rs index 2c132324d44a..0d38e77b7b71 100644 --- a/runtime/parachains/src/ump/benchmarking.rs +++ b/runtime/parachains/src/ump/benchmarking.rs @@ -42,7 +42,7 @@ 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, + origin_kind: OriginKind::SovereignAccount, require_weight_at_most: Weight::MAX, call: frame_system::Call::::remark_with_event { remark: vec![] }.encode().into(), }])) @@ -54,7 +54,7 @@ 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, + origin_kind: OriginKind::SovereignAccount, require_weight_at_most: Weight::MAX, call: frame_system::Call::::remark_with_event { remark }.encode().into(), }])) @@ -69,7 +69,7 @@ fn create_message_overweight() -> Vec { // 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, + origin_kind: OriginKind::Superuser, require_weight_at_most: max_block_weight, call: call.encode().into(), }])) diff --git a/runtime/polkadot/src/xcm_config.rs b/runtime/polkadot/src/xcm_config.rs index f75d30d079f4..f8f22149ab52 100644 --- a/runtime/polkadot/src/xcm_config.rs +++ b/runtime/polkadot/src/xcm_config.rs @@ -17,8 +17,8 @@ //! XCM configuration for Polkadot. use super::{ - parachains_origin, AccountId, Balances, Call, CouncilCollective, Event, Origin, ParaId, - Runtime, WeightToFee, XcmPallet, + parachains_origin, AccountId, AllPalletsWithSystem, Balances, Call, CouncilCollective, Event, + Origin, ParaId, Runtime, WeightToFee, XcmPallet, }; use frame_support::{ match_types, parameter_types, @@ -31,20 +31,19 @@ use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, BackingToPlurality, ChildParachainAsNative, ChildParachainConvertsVia, CurrencyAdapter as XcmCurrencyAdapter, FixedWeightBounds, - IsConcrete, LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, + IsConcrete, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, + TakeWeightCredit, UsingComponents, }; 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(); + pub const ThisNetwork: NetworkId = NetworkId::Polkadot; + /// Our location in the universe of consensus systems. + pub const UniversalLocation: InteriorMultiLocation = X1(GlobalConsensus(ThisNetwork::get())); /// The check account, which holds any native assets that have been teleported out and not back in (yet). pub CheckAccount: AccountId = XcmPallet::check_account(); } @@ -55,18 +54,18 @@ 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): @@ -87,7 +86,7 @@ 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! { @@ -102,16 +101,17 @@ 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 Dot: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete(TokenLocation::get()) }); + pub const DotForStatemint: (MultiAssetFilter, MultiLocation) = (Dot::get(), Parachain(1000).into_location()); + pub const MaxAssetsIntoHolding: u32 = 64; } /// Polkadot Relay recognizes/respects the Statemint chain as a teleporter. -pub type TrustedTeleporters = (xcm_builder::Case,); +pub type TrustedTeleporters = (xcm_builder::Case,); match_types! { pub type OnlyParachains: impl Contains = { @@ -140,21 +140,28 @@ 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; } parameter_types! { pub const CouncilBodyId: BodyId = BodyId::Executive; - // We are conservative with the XCM version we advertize. - pub const AdvertisedXcmVersion: u32 = 2; } /// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior location @@ -168,7 +175,7 @@ pub type LocalOriginToLocation = ( CouncilBodyId, >, // And a usual Signed origin to be used in XCM as a corresponding AccountId32 - SignedToAccountId32, + SignedToAccountId32, ); impl pallet_xcm::Config for Runtime { @@ -184,9 +191,14 @@ 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 Origin = Origin; type Call = Call; 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 = frame_support::traits::ConstU32<8>; } diff --git a/runtime/rococo/src/xcm_config.rs b/runtime/rococo/src/xcm_config.rs index 1512ef9e6ccd..8e71ca8daffe 100644 --- a/runtime/rococo/src/xcm_config.rs +++ b/runtime/rococo/src/xcm_config.rs @@ -17,8 +17,8 @@ //! XCM configuration for Rococo. use super::{ - parachains_origin, AccountId, Balances, Call, Event, Origin, ParaId, Runtime, WeightToFee, - XcmPallet, + parachains_origin, AccountId, AllPalletsWithSystem, Balances, Call, Event, Origin, ParaId, + Runtime, WeightToFee, XcmPallet, }; use frame_support::{ parameter_types, @@ -31,27 +31,28 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, BackingToPlurality, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - CurrencyAdapter as XcmCurrencyAdapter, FixedWeightBounds, IsConcrete, LocationInverter, + CurrencyAdapter as XcmCurrencyAdapter, FixedWeightBounds, IsConcrete, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, UsingComponents, }; +use xcm_executor::XcmExecutor; parameter_types! { - pub const RocLocation: MultiLocation = Here.into(); - pub const RococoNetwork: NetworkId = NetworkId::Polkadot; - pub const Ancestry: MultiLocation = Here.into(); + 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 type SovereignAccountOf = - (ChildParachainConvertsVia, AccountId32Aliases); +pub type LocationConverter = + (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: - SovereignAccountOf, + 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. @@ -59,9 +60,9 @@ pub type LocalAssetTransactor = XcmCurrencyAdapter< >; type LocalOriginConverter = ( - SovereignSignedViaLocation, + SovereignSignedViaLocation, ChildParachainAsNative, - SignedAccountId32AsNative, + SignedAccountId32AsNative, ChildSystemParachainAsSuperuser, ); @@ -73,26 +74,27 @@ 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 Rococo: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete(RocLocation::get()) }); - pub const RococoForTick: (MultiAssetFilter, MultiLocation) = (Rococo::get(), Parachain(100).into()); - pub const RococoForTrick: (MultiAssetFilter, MultiLocation) = (Rococo::get(), Parachain(110).into()); - pub const RococoForTrack: (MultiAssetFilter, MultiLocation) = (Rococo::get(), Parachain(120).into()); - pub const RococoForStatemine: (MultiAssetFilter, MultiLocation) = (Rococo::get(), Parachain(1000).into()); - pub const RococoForCanvas: (MultiAssetFilter, MultiLocation) = (Rococo::get(), Parachain(1002).into()); - pub const RococoForEncointer: (MultiAssetFilter, MultiLocation) = (Rococo::get(), Parachain(1003).into()); + pub const Roc: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete(TokenLocation::get()) }); + pub const RocForTick: (MultiAssetFilter, MultiLocation) = (Roc::get(), Parachain(100).into_location()); + pub const RocForTrick: (MultiAssetFilter, MultiLocation) = (Roc::get(), Parachain(110).into_location()); + pub const RocForTrack: (MultiAssetFilter, MultiLocation) = (Roc::get(), Parachain(120).into_location()); + pub const RocForRockmine: (MultiAssetFilter, MultiLocation) = (Roc::get(), Parachain(1000).into_location()); + pub const RocForCanvas: (MultiAssetFilter, MultiLocation) = (Roc::get(), Parachain(1002).into_location()); + pub const RocForEncointer: (MultiAssetFilter, MultiLocation) = (Roc::get(), Parachain(1003).into_location()); 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, ); parameter_types! { @@ -126,14 +128,22 @@ 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 = 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; } parameter_types! { @@ -147,7 +157,7 @@ pub type LocalOriginToLocation = ( // `Unit` body. BackingToPlurality, CollectiveBodyId>, // And a usual Signed origin to be used in XCM as a corresponding AccountId32 - SignedToAccountId32, + SignedToAccountId32, ); impl pallet_xcm::Config for Runtime { @@ -158,13 +168,18 @@ impl pallet_xcm::Config for Runtime { type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; // ...but they must match our filter, which right now rejects everything. type XcmExecuteFilter = Nothing; - type XcmExecutor = xcm_executor::XcmExecutor; + type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; 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 = frame_support::traits::ConstU32<8>; } diff --git a/runtime/test-runtime/src/lib.rs b/runtime/test-runtime/src/lib.rs index 9d6445739675..1acc5bd16a43 100644 --- a/runtime/test-runtime/src/lib.rs +++ b/runtime/test-runtime/src/lib.rs @@ -525,8 +525,9 @@ impl parachains_ump::Config for Runtime { parameter_types! { pub const BaseXcmWeight: frame_support::weights::Weight = 1_000; - pub const AnyNetwork: xcm::latest::NetworkId = xcm::latest::NetworkId::Any; + pub const AnyNetwork: Option = None; pub const MaxInstructions: u32 = 100; + pub const UniversalLocation: xcm::latest::InteriorMultiLocation = xcm::latest::Junctions::Here; } pub type LocalOriginToLocation = xcm_builder::SignedToAccountId32; @@ -536,7 +537,7 @@ impl pallet_xcm::Config for Runtime { // is `XcmExecutor`, which will be used in unit tests located in xcm-executor. type Event = Event; type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; - type LocationInverter = xcm_config::InvertNothing; + type UniversalLocation = UniversalLocation; type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; type Weigher = xcm_builder::FixedWeightBounds; type XcmRouter = xcm_config::DoNothingRouter; @@ -548,6 +549,11 @@ impl pallet_xcm::Config for Runtime { type Call = Call; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = (); + type MaxLockers = frame_support::traits::ConstU32<8>; } impl parachains_hrmp::Config for Runtime { @@ -612,8 +618,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(()) @@ -628,9 +635,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 }, ::Call::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 2faf3ffd0606..284a1d3dc21b 100644 --- a/runtime/test-runtime/src/xcm_config.rs +++ b/runtime/test-runtime/src/xcm_config.rs @@ -14,17 +14,23 @@ // 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, weights::Weight}; +use frame_support::{ + parameter_types, + traits::{Everything, Nothing}, + weights::Weight, +}; use xcm::latest::prelude::*; use xcm_builder::{AllowUnpaidExecutionFrom, FixedWeightBounds, SignedToAccountId32}; use xcm_executor::{ - traits::{InvertLocation, TransactAsset, WeightTrader}, + traits::{TransactAsset, WeightTrader}, Assets, }; parameter_types! { pub const OurNetwork: NetworkId = NetworkId::Polkadot; 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 @@ -36,8 +42,12 @@ pub type LocalOriginToLocation = ( 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 +55,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()) } @@ -66,16 +80,6 @@ impl WeightTrader for DummyWeightTrader { } } -pub struct InvertNothing; -impl InvertLocation for InvertNothing { - fn invert_location(_: &MultiLocation) -> sp_std::result::Result { - Ok(Here.into()) - } - fn ancestry() -> MultiLocation { - Here.into() - } -} - pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type Call = super::Call; @@ -84,12 +88,19 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = pallet_xcm::XcmPassthrough; type IsReserve = (); type IsTeleporter = (); - type LocationInverter = InvertNothing; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; 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; } diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index f9a4aba12dff..c4a853eafbc6 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -1546,10 +1546,10 @@ sp_api::impl_runtime_apis! { impl frame_system_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; @@ -1557,10 +1557,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() } @@ -1569,11 +1569,7 @@ 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()) }, )); } @@ -1582,11 +1578,10 @@ sp_api::impl_runtime_apis! { type CheckedAccount = xcm_config::CheckAccount; type TrustedTeleporter = TrustedTeleporter; - type TrustedReserve = TrustedReserve; fn get_multi_asset() -> MultiAsset { MultiAsset { - id: Concrete(WndLocation::get()), + id: Concrete(TokenLocation::get()), fun: Fungible(1 * UNITS), } } @@ -1599,6 +1594,16 @@ sp_api::impl_runtime_apis! { (0u64, Response::Version(Default::default())) } + 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() -> Result { Ok(Westmint::get()) } @@ -1609,10 +1614,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 32a8e42e0043..60803d7a8e4c 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() { @@ -59,9 +59,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/xcm/mod.rs b/runtime/westend/src/weights/xcm/mod.rs index a06120b7b0ad..4dcfaf6889f0 100644 --- a/runtime/westend/src/weights/xcm/mod.rs +++ b/runtime/westend/src/weights/xcm/mod.rs @@ -4,7 +4,10 @@ mod pallet_xcm_benchmarks_generic; use crate::Runtime; use frame_support::weights::Weight; use sp_std::prelude::*; -use xcm::{latest::prelude::*, DoubleEncoded}; +use xcm::{ + latest::{prelude::*, QueryResponseInfo}, + DoubleEncoded, +}; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmBalancesWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; @@ -75,7 +78,12 @@ impl XcmWeightInfo for WestendXcmWeight { 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) -> Weight { + fn query_response( + _query_id: &u64, + _response: &Response, + _max_weight: &u64, + _querier: &Option, + ) -> Weight { XcmGeneric::::query_response() } fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { @@ -89,7 +97,7 @@ impl XcmWeightInfo for WestendXcmWeight { assets.weigh_multi_assets(XcmBalancesWeight::::transfer_reserve_asset()) } fn transact( - _origin_type: &OriginKind, + _origin_kind: &OriginKind, _require_weight_at_most: &u64, _call: &DoubleEncoded, ) -> Weight { @@ -117,31 +125,23 @@ impl XcmWeightInfo for WestendXcmWeight { fn descend_origin(_who: &InteriorMultiLocation) -> Weight { XcmGeneric::::descend_origin() } - fn report_error( - _query_id: &QueryId, - _dest: &MultiLocation, - _max_response_weight: &u64, - ) -> Weight { + 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, - ) -> Weight { + 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<()>, ) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets) -> Weight { - Weight::MAX // 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, @@ -157,13 +157,8 @@ impl XcmWeightInfo for WestendXcmWeight { ) -> Weight { assets.weigh_multi_assets(XcmBalancesWeight::::initiate_teleport()) } - fn query_holding( - _query_id: &u64, - _dest: &MultiLocation, - _assets: &MultiAssetFilter, - _max_response_weight: &u64, - ) -> Weight { - XcmGeneric::::query_holding() + fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + XcmGeneric::::report_holding() } fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { XcmGeneric::::buy_execution() @@ -192,4 +187,70 @@ impl XcmWeightInfo for WestendXcmWeight { 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 { + Weight::MAX // todo fix + } + fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Westend does not currently support asset locking operations + Weight::MAX + } + fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Westend does not currently support asset locking operations + Weight::MAX + } + fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Westend does not currently support asset locking operations + Weight::MAX + } + fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + // Westend does not currently support asset locking operations + Weight::MAX + } + fn set_fees_mode(_: &bool) -> Weight { + XcmGeneric::::set_fees_mode() + } + fn set_topic(_topic: &[u8; 32]) -> Weight { + XcmGeneric::::set_topic() + } + fn clear_topic() -> Weight { + XcmGeneric::::clear_topic() + } + fn alias_origin(_: &MultiLocation) -> Weight { + // XCM Executor does not currently support alias origin operations + Weight::MAX + } } 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 d87cb6d37861..73a615cf0dee 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,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: 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-04-17, 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 +// target/production/polkadot // benchmark +// pallet // --chain=westend-dev // --steps=50 // --repeat=20 @@ -50,39 +51,39 @@ impl WeightInfo { // 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 { - (39_278_000 as Weight) + pub(crate) fn report_holding() -> Weight { + (24_686_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } pub(crate) fn buy_execution() -> Weight { - (5_922_000 as Weight) + (3_620_000 as Weight) } // Storage: XcmPallet Queries (r:1 w:0) pub(crate) fn query_response() -> Weight { - (20_625_000 as Weight) + (12_946_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } pub(crate) fn transact() -> Weight { - (22_198_000 as Weight) + (13_206_000 as Weight) } pub(crate) fn refund_surplus() -> Weight { - (6_122_000 as Weight) + (3_879_000 as Weight) } pub(crate) fn set_error_handler() -> Weight { - (5_758_000 as Weight) + (3_659_000 as Weight) } pub(crate) fn set_appendix() -> Weight { - (5_764_000 as Weight) + (3_743_000 as Weight) } pub(crate) fn clear_error() -> Weight { - (5_679_000 as Weight) + (3_711_000 as Weight) } pub(crate) fn descend_origin() -> Weight { - (7_206_000 as Weight) + (4_618_000 as Weight) } pub(crate) fn clear_origin() -> Weight { - (5_738_000 as Weight) + (3_588_000 as Weight) } // Storage: XcmPallet SupportedVersion (r:1 w:0) // Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1) @@ -90,18 +91,18 @@ impl WeightInfo { // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) // Storage: Dmp DownwardMessageQueues (r:1 w:1) pub(crate) fn report_error() -> Weight { - (31_512_000 as Weight) + (20_331_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: XcmPallet AssetTraps (r:1 w:1) pub(crate) fn claim_asset() -> Weight { - (13_594_000 as Weight) + (7_650_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } pub(crate) fn trap() -> Weight { - (5_745_000 as Weight) + (3_617_000 as Weight) } // Storage: XcmPallet VersionNotifyTargets (r:1 w:1) // Storage: XcmPallet SupportedVersion (r:1 w:0) @@ -110,13 +111,13 @@ impl WeightInfo { // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) // Storage: Dmp DownwardMessageQueues (r:1 w:1) pub(crate) fn subscribe_version() -> Weight { - (38_138_000 as Weight) + (28_613_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: XcmPallet VersionNotifyTargets (r:0 w:1) pub(crate) fn unsubscribe_version() -> Weight { - (9_127_000 as Weight) + (5_443_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: XcmPallet SupportedVersion (r:1 w:0) @@ -125,8 +126,55 @@ impl WeightInfo { // Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) // Storage: Dmp DownwardMessageQueues (r:1 w:1) pub(crate) fn initiate_reserve_withdraw() -> Weight { - (41_443_000 as Weight) + (24_401_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } + pub(crate) fn burn_asset() -> Weight { + (5_259_000 as Weight) + } + pub(crate) fn expect_asset() -> Weight { + (3_745_000 as Weight) + } + pub(crate) fn expect_origin() -> Weight { + (3_847_000 as Weight) + } + pub(crate) fn expect_error() -> Weight { + (3_633_000 as Weight) + } + // 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 { + (21_645_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + pub(crate) fn expect_pallet() -> Weight { + (4_017_000 as Weight) + } + // 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 { + (20_465_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + pub(crate) fn clear_transact_status() -> Weight { + (3_723_000 as Weight) + } + pub(crate) fn set_topic() -> Weight { + (3_687_000 as Weight) + } + pub(crate) fn clear_topic() -> Weight { + (3_654_000 as Weight) + } + pub(crate) fn set_fees_mode() -> Weight { + (3_721_000 as Weight) + } } diff --git a/runtime/westend/src/xcm_config.rs b/runtime/westend/src/xcm_config.rs index cbe0ed31f5cd..ecf1a8be21bb 100644 --- a/runtime/westend/src/xcm_config.rs +++ b/runtime/westend/src/xcm_config.rs @@ -17,8 +17,8 @@ //! XCM configurations for Westend. use super::{ - parachains_origin, weights, AccountId, Balances, Call, Event, Origin, ParaId, Runtime, - WeightToFee, XcmPallet, + parachains_origin, weights, AccountId, AllPalletsWithSystem, Balances, Call, Event, Origin, + ParaId, Runtime, WeightToFee, XcmPallet, }; use frame_support::{ parameter_types, @@ -30,26 +30,27 @@ use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - CurrencyAdapter as XcmCurrencyAdapter, IsChildSystemParachain, IsConcrete, LocationInverter, + CurrencyAdapter as XcmCurrencyAdapter, IsChildSystemParachain, IsConcrete, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, WeightInfoBounds, }; +use xcm_executor::XcmExecutor; parameter_types! { - pub const WndLocation: MultiLocation = Here.into(); - pub const Ancestry: MultiLocation = Here.into(); - pub WestendNetwork: NetworkId = NetworkId::Named(b"Westend".to_vec()); + 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 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): @@ -61,7 +62,7 @@ pub type LocalAssetTransactor = XcmCurrencyAdapter< type LocalOriginConverter = ( SovereignSignedViaLocation, ChildParachainAsNative, - SignedAccountId32AsNative, + SignedAccountId32AsNative, ChildSystemParachainAsSuperuser, ); @@ -69,20 +70,20 @@ 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 Encointer: MultiLocation = Parachain(1001).into(); - pub const WestendForWestmint: (MultiAssetFilter, MultiLocation) = - (Wild(AllOf { fun: WildFungible, id: Concrete(WndLocation::get()) }), Westmint::get()); - pub const WestendForEncointer: (MultiAssetFilter, MultiLocation) = - (Wild(AllOf { fun: WildFungible, id: Concrete(WndLocation::get()) }), Encointer::get()); + pub const Westmint: MultiLocation = Parachain(1000).into_location(); + pub const Encointer: 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 WndForEncointer: (MultiAssetFilter, MultiLocation) = (Wnd::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); /// The barriers one of which must be passed for an XCM message to be executed. pub type Barrier = ( @@ -106,21 +107,29 @@ 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, Call, 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 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 { @@ -131,13 +140,18 @@ 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, Call, MaxInstructions>; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; 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 = frame_support::traits::ConstU32<8>; } diff --git a/scripts/gitlab/lingua.dic b/scripts/gitlab/lingua.dic index bf70b0512e40..031018e952a6 100644 --- a/scripts/gitlab/lingua.dic +++ b/scripts/gitlab/lingua.dic @@ -160,6 +160,7 @@ MQC/SM msg multisig/S multivalidator/SM +mutators mutex natively NFA @@ -215,6 +216,7 @@ proxy/G proxying PRs PVF/S +querier README/MS redhat/M register/CD @@ -247,6 +249,7 @@ SS58 SSL startup/MS stateful +Statemine str struct/MS subcommand/SM @@ -282,6 +285,7 @@ UDP UI unassign unconcluded +unexpectable unfinalize/B unfinalized union/MSG diff --git a/xcm/Cargo.toml b/xcm/Cargo.toml index 4c9098e337d6..164fd0d253ae 100644 --- a/xcm/Cargo.toml +++ b/xcm/Cargo.toml @@ -7,9 +7,11 @@ edition = "2021" [dependencies] impl-trait-for-tuples = "0.2.2" -parity-scale-codec = { version = "3.1.2", default-features = false, features = [ "derive" ] } +parity-scale-codec = { version = "3.1.2", default-features = false, features = [ "derive", "max-encoded-len" ] } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } derivative = {version = "2.2.0", default-features = false, features = [ "use_core" ] } +serde = { version = "1.0.136", optional = true, features = ["derive"] } log = { version = "0.4.16", default-features = false } xcm-procedural = { path = "procedural" } @@ -20,4 +22,6 @@ runtime-benchmarks = [] std = [ "parity-scale-codec/std", "scale-info/std", + "serde", + "sp-io/std" ] diff --git a/xcm/pallet-xcm-benchmarks/Cargo.toml b/xcm/pallet-xcm-benchmarks/Cargo.toml index c6652d2c222a..2ff0d0b281df 100644 --- a/xcm/pallet-xcm-benchmarks/Cargo.toml +++ b/xcm/pallet-xcm-benchmarks/Cargo.toml @@ -14,6 +14,7 @@ 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, features = ["runtime-benchmarks"] } frame-benchmarking = { default-features = false, branch = "master", git = "https://github.com/paritytech/substrate" } xcm = { path = "..", default-features = false, features = ["runtime-benchmarks"] } @@ -23,7 +24,6 @@ log = "0.4.16" 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 = ".." } @@ -40,6 +40,8 @@ std = [ "frame-benchmarking/std", "frame-support/std", "frame-system/std", + "sp-io/std", "sp-runtime/std", - "sp-std/std" + "sp-std/std", + "xcm-executor/std" ] diff --git a/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs b/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs index d97ac34b2651..7a68e7ed939e 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,32 +124,13 @@ 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() @@ -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,7 +217,7 @@ 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()); @@ -223,7 +226,7 @@ benchmarks_instance_pallet! { 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,7 +234,7 @@ benchmarks_instance_pallet! { }; let xcm = Xcm(vec![instruction]); }: { - executor.execute(xcm)?; + executor.bench_process(xcm)?; } verify { if let Some(checked_account) = T::CheckedAccount::get() { // teleport checked account should have received some asset. diff --git a/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs b/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs index 3f7e4f8f26e0..8a657c5163bf 100644 --- a/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs +++ b/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs @@ -18,7 +18,10 @@ use crate::{fungible as xcm_balances_benchmark, mock::*}; use frame_benchmarking::BenchmarkError; -use frame_support::{parameter_types, traits::Everything}; +use frame_support::{ + parameter_types, + traits::{Everything, Nothing}, +}; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -124,6 +127,7 @@ 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; @@ -132,16 +136,23 @@ 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; } impl crate::Config for Test { @@ -149,40 +160,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 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 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 1acf61cf0010..e5062c310494 100644 --- a/xcm/pallet-xcm-benchmarks/src/fungible/mod.rs +++ b/xcm/pallet-xcm-benchmarks/src/fungible/mod.rs @@ -38,10 +38,6 @@ pub mod pallet { /// 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 62d0df265c42..05db22b287ac 100644 --- a/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs +++ b/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs @@ -19,31 +19,35 @@ use crate::{new_executor, XcmCallOf}; use codec::Encode; use frame_benchmarking::{benchmarks, BenchmarkError}; use frame_support::dispatch::GetDispatchInfo; +use sp_io::hashing::blake2_256; use sp_std::vec; use xcm::{ - latest::{prelude::*, MultiAssets}, + latest::{prelude::*, MaybeErrorCode, MultiAssets}, 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: u64::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. } @@ -51,21 +55,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 { } @@ -76,7 +80,7 @@ benchmarks! { const MAX_ASSETS: u32 = 100; // TODO when executor has a built in limit, use it here. #4426 let mut executor = new_executor::(Default::default()); let assets = (0..MAX_ASSETS).map(|i| MultiAsset { - id: Abstract(i.encode()), + id: Abstract(i.using_encoded(blake2_256)), fun: Fungible(i as u128), }).collect::>(); @@ -85,19 +89,20 @@ benchmarks! { let instruction = Instruction::ReserveAssetDeposited(multiassets.clone()); let xcm = Xcm(vec![instruction]); }: { - executor.execute(xcm).map_err(|_| BenchmarkError::Skip)?; + executor.bench_process(xcm).map_err(|_| BenchmarkError::Skip)?; } verify { - assert_eq!(executor.holding, multiassets.into()); + assert_eq!(executor.holding(), &multiassets.into()); } 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 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 } @@ -114,7 +119,7 @@ benchmarks! { let double_encoded_noop_call: DoubleEncoded<_> = noop_call.encode().into(); let instruction = Instruction::Transact { - origin_type: OriginKind::SovereignAccount, + origin_kind: OriginKind::SovereignAccount, require_weight_at_most: noop_call.get_dispatch_info().weight, call: double_encoded_noop_call, }; @@ -122,7 +127,7 @@ benchmarks! { 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(); @@ -130,19 +135,19 @@ benchmarks! { } 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(1337); + executor.set_total_refunded(0); 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(), &1337); + assert_eq!(executor.total_refunded(), &1337); } set_error_handler { @@ -150,9 +155,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 { @@ -161,20 +166,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 { @@ -183,11 +188,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, }), @@ -199,22 +204,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 } @@ -228,6 +235,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. @@ -236,9 +248,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 { @@ -248,14 +260,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 { @@ -267,7 +277,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)); } @@ -281,7 +291,12 @@ benchmarks! { ::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)); @@ -289,30 +304,316 @@ 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 + } + 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 7782ba1d90ea..26b69fc527aa 100644 --- a/xcm/pallet-xcm-benchmarks/src/generic/mock.rs +++ b/xcm/pallet-xcm-benchmarks/src/generic/mock.rs @@ -29,7 +29,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; @@ -84,17 +87,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; @@ -105,14 +113,22 @@ 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; } impl crate::Config for Test { @@ -120,12 +136,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(), + ) } } @@ -137,6 +156,14 @@ impl generic::Config for Test { (0, Response::Assets(assets)) } + fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + Ok(Default::default()) + } + + fn universal_alias() -> Result { + Ok(GlobalConsensus(ByGenesis([0; 32]))) + } + fn transact_origin() -> Result { Ok(Default::default()) } @@ -150,6 +177,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 270eaf896e7e..cf5c03d2757c 100644 --- a/xcm/pallet-xcm-benchmarks/src/generic/mod.rs +++ b/xcm/pallet-xcm-benchmarks/src/generic/mod.rs @@ -8,7 +8,7 @@ mod mock; pub mod pallet { use frame_benchmarking::BenchmarkError; use frame_support::{dispatch::Dispatchable, pallet_prelude::Encode, weights::GetDispatchInfo}; - use xcm::latest::{MultiAssets, MultiLocation, Response}; + use xcm::latest::{Junction, MultiAsset, MultiAssets, MultiLocation, Response}; #[pallet::config] pub trait Config: frame_system::Config + crate::Config { @@ -20,6 +20,20 @@ pub mod pallet { /// The response which causes the most runtime weight. fn worst_case_response() -> (u64, Response); + /// The pair of asset collections which causes the most runtime weight if demanded to be + /// exchanged. + /// + /// 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 `None`, 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 `None`, benchmarks which rely on a universal alais will be skipped. + fn universal_alias() -> Result; + /// The `MultiLocation` used for successful transaction XCMs. /// /// If set to `None`, benchmarks which rely on a `transact_origin` will be skipped. @@ -32,6 +46,9 @@ pub mod pallet { /// 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 8492dac5a622..4759199c7d63 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 = ::Call; /// 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>::Call; +pub type XcmCallOf = <::XcmConfig as XcmConfig>::Call; -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 d59cf3387268..718f232f0779 100644 --- a/xcm/pallet-xcm-benchmarks/src/mock.rs +++ b/xcm/pallet-xcm-benchmarks/src/mock.rs @@ -15,22 +15,32 @@ // along with Polkadot. If not, see . use crate::*; -use frame_support::{parameter_types, weights::Weight}; -use xcm_executor::traits::FilterAssetLocation; +use frame_support::{parameter_types, traits::ContainsPair, weights::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, _: Weight) -> Weight { + fn on_response( + _: &MultiLocation, + _: u64, + _: Option<&MultiLocation>, + _: Response, + _: Weight, + _: &XcmContext, + ) -> Weight { 0 } } @@ -51,14 +61,14 @@ impl xcm_executor::traits::Convert for AccountIdConverter { } parameter_types! { - pub Ancestry: MultiLocation = Junction::Parachain(101).into(); + pub UniversalLocation: InteriorMultiLocation = Junction::Parachain(101).into(); pub UnitWeightCost: Weight = 10; pub WeightPrice: (AssetId, u128) = (Concrete(Here.into()), 1_000_000); } 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 7da87c37f447..f37313e21b26 100644 --- a/xcm/pallet-xcm/Cargo.toml +++ b/xcm/pallet-xcm/Cargo.toml @@ -10,11 +10,12 @@ scale-info = { version = "2.1.1", default-features = false, features = ["derive" serde = { version = "1.0.136", optional = true, features = ["derive"] } log = { version = "0.4.16", 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-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 } @@ -22,9 +23,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"] @@ -34,6 +34,7 @@ std = [ "serde", "sp-std/std", "sp-core/std", + "sp-io/std", "sp-runtime/std", "frame-support/std", "frame-system/std", diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 9fe7f72d42ba..729531a974c8 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -24,37 +24,45 @@ mod mock; mod tests; use codec::{Decode, Encode, EncodeLike}; -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::{BadOrigin, Saturating, Zero}, RuntimeDebug, }; use sp_std::{boxed::Box, marker::PhantomData, prelude::*, result::Result, vec}; -use xcm::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 sp_runtime::traits::{AccountIdConversion, BlakeTwo256, BlockNumberProvider, Hash}; +use xcm_executor::{ + traits::{ + CheckSuspension, ClaimAssets, DropAssets, MatchesFungible, OnResponse, + VersionChangeNotifier, WeightBounds, + }, + Assets, +}; #[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 @@ -67,12 +75,22 @@ pub mod pallet { #[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 Event: From> + IsType<::Event>; + /// 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<::Origin, Success = MultiLocation>; @@ -100,8 +118,8 @@ pub mod pallet { /// Means of measuring the weight consumed by an XCM message locally. type Weigher: WeightBounds<::Call>; - /// Means of inverting a location. - type LocationInverter: InvertLocation; + /// This chain's Universal Location. + type UniversalLocation: Get; /// The outer `Origin` type. type Origin: From + From<::Origin>; @@ -117,10 +135,17 @@ 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; + } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -192,8 +217,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. /// @@ -209,6 +236,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), } #[pallet::origin] @@ -256,6 +316,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. @@ -263,7 +347,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, }, @@ -381,6 +470,42 @@ 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, + >; + + /// Global suspension state of the XCM executor. + #[pallet::storage] + #[pallet::getter(fn suspended)] + pub(super) type XcmExecutionSuspended = StorageValue<_, bool, ValueQuery>; + #[pallet::genesis_config] pub struct GenesisConfig { /// The default version to encode outgoing XCM messages with. @@ -443,6 +568,77 @@ 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 Weight + 1, count as Weight + 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::weight(100_000_000)] @@ -457,10 +653,7 @@ pub mod pallet { 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.clone(), message.clone()).map_err(Error::::from)?; Self::deposit_event(Event::Sent(origin_location, dest, message)); Ok(()) } @@ -486,9 +679,14 @@ 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_value(), |w| 100_000_000 + w) }, @@ -568,6 +766,7 @@ pub mod pallet { 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); @@ -575,6 +774,7 @@ pub mod pallet { let outcome = T::XcmExecutor::execute_xcm_in_credit( origin_location, message, + hash, max_weight, max_weight, ); @@ -665,6 +865,17 @@ pub mod pallet { }) } + /// Set or unset the global suspension state of the XCM executor. + /// + /// - `origin`: Must be Root. + /// - `suspended`: `true` to suspend, `false` to resume. + #[pallet::weight(100_000_000u64)] + pub fn force_suspension(origin: OriginFor, suspended: bool) -> DispatchResult { + ensure_root(origin)?; + XcmExecutionSuspended::::set(suspended); + Ok(()) + } + /// Transfer some assets from the local chain to the sovereign account of a destination /// chain and forward a notification XCM. /// @@ -763,704 +974,983 @@ 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 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(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 = 0; + 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 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(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().read + T::DbWeight::get().write; - let todo_vn_migrate_weight: Weight = T::DbWeight::get().read + T::DbWeight::get().write; - let todo_vnt_already_notified_weight: Weight = T::DbWeight::get().read; - let todo_vnt_notify_weight: Weight = - T::DbWeight::get().read + T::DbWeight::get().write * 3; - let todo_vnt_migrate_weight: Weight = - T::DbWeight::get().read + T::DbWeight::get().write; - let todo_vnt_migrate_fail_weight: Weight = - T::DbWeight::get().read + T::DbWeight::get().write; - let todo_vnt_notify_migrate_weight: Weight = - T::DbWeight::get().read + T::DbWeight::get().write * 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 >= 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 = 0; + + // TODO: Correct weights for the components of this: + let todo_sv_migrate_weight: Weight = T::DbWeight::get().read + T::DbWeight::get().write; + let todo_vn_migrate_weight: Weight = T::DbWeight::get().read + T::DbWeight::get().write; + let todo_vnt_already_notified_weight: Weight = T::DbWeight::get().read; + let todo_vnt_notify_weight: Weight = T::DbWeight::get().read + T::DbWeight::get().write * 3; + let todo_vnt_migrate_weight: Weight = T::DbWeight::get().read + T::DbWeight::get().write; + let todo_vnt_migrate_fail_weight: Weight = + T::DbWeight::get().read + T::DbWeight::get().write; + let todo_vnt_notify_migrate_weight: Weight = + T::DbWeight::get().read + T::DbWeight::get().write * 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 >= 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 >= 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 >= 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(todo_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.clone(), 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(todo_vnt_notify_weight); + if weight_used >= 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(todo_vnt_migrate_fail_weight); + if weight_used >= 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 >= 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 >= 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(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, + querier: None, + }]); + let event = match send_xcm::(new_key.clone(), 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 >= weight_cutoff { - return (weight_used, Some(stage)) - } + Self::deposit_event(event); + weight_used.saturating_accrue(todo_vnt_notify_migrate_weight); + } + if weight_used >= 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.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 }; + let (_hash, cost) = send_xcm::(dest.clone(), 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.clone(), 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.clone())); + 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(&ID) - } + pub fn check_account() -> T::AccountId { + const ID: PalletId = PalletId(*b"py/xcmch"); + AccountIdConversion::::into_account(&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::MultiLocationNotInvertible)?; + let query_id = Self::new_query(responder, timeout, Here); + let response_info = QueryResponseInfo { destination, query_id, max_weight: 0 }; + 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<::Call>, - timeout: T::BlockNumber, - ) -> Result<(), XcmError> { - let responder = responder.into(); - let dest = T::LocationInverter::invert_location(&responder) - .map_err(|()| XcmError::MultiLocationNotInvertible)?; - let notify: ::Call = 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 }]); - 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<::Call>, + timeout: T::BlockNumber, + ) -> Result<(), XcmError> { + let responder = responder.into(); + let destination = T::UniversalLocation::get() + .invert_target(&responder) + .map_err(|()| XcmError::MultiLocationNotInvertible)?; + let notify: ::Call = 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<::Call>, - 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<::Call>, + 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.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 { - 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.clone(), 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.clone().into())) + .map_err(|()| 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) -> Weight { - 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: u64, + _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.clone(), Xcm(vec![instruction]))?; + Self::deposit_event(Event::::VersionNotifyStarted(dest.clone(), 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 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 } +} - 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)); - match AssetTraps::::get(hash) { - 0 => return false, - 1 => AssetTraps::::remove(hash), - n => AssetTraps::::insert(hash, n - 1), - } - 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)); + match AssetTraps::::get(hash) { + 0 => return false, + 1 => AssetTraps::::remove(hash), + n => AssetTraps::::insert(hash, n - 1), } + 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: Weight, - ) -> 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.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( + 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.clone(), 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, + 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, + }, ); - 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)); + 0 + }, + ( + 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( + Self::deposit_event(Event::InvalidQuerierVersion( origin.clone(), query_id, )); return 0 }, }; - if origin != &responder { - Self::deposit_event(Event::InvalidResponder( + if querier.map_or(true, |q| q != &match_querier) { + Self::deposit_event(Event::InvalidQuerier( origin.clone(), query_id, - Some(responder), + match_querier, + querier.cloned(), )); return 0 } - 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| ::Call::decode(&mut bytes)) - { - Queries::::remove(query_id); - let weight = call.get_dispatch_info().weight; - if weight > max_weight { - let e = Event::NotifyOverweight( + } + let responder = match MultiLocation::try_from(responder) { + Ok(r) => r, + Err(_) => { + Self::deposit_event(Event::InvalidResponderVersion( + origin.clone(), + query_id, + )); + return 0 + }, + }; + if origin != &responder { + Self::deposit_event(Event::InvalidResponder( + origin.clone(), + query_id, + Some(responder), + )); + return 0 + } + 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| ::Call::decode(&mut bytes)) + { + Queries::::remove(query_id); + let weight = call.get_dispatch_info().weight; + if weight > max_weight { + let e = Event::NotifyOverweight( + 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, - 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) - } 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 - }, - } + } + }, + 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 }); + 0 + }, + } + }, + _ => { + Self::deposit_event(Event::UnexpectedResponse(origin.clone(), query_id)); + return 0 + }, } } } +impl CheckSuspension for Pallet { + fn is_suspended( + _origin: &MultiLocation, + _instructions: &mut [Instruction], + _max_weight: Weight, + _weight_credit: &mut Weight, + ) -> bool { + Self::suspended() + } +} + /// Ensure that the origin `o` represents an XCM (`Transact`) origin. /// /// Returns `Ok` with the location of the XCM sender or an `Err` otherwise. diff --git a/xcm/pallet-xcm/src/mock.rs b/xcm/pallet-xcm/src/mock.rs index 5627acd31a3c..2a6018ac35f1 100644 --- a/xcm/pallet-xcm/src/mock.rs +++ b/xcm/pallet-xcm/src/mock.rs @@ -14,19 +14,24 @@ // 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, weights::Weight}; +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; @@ -74,21 +79,25 @@ pub mod pallet_test_notifier { #[pallet::call] impl Pallet { #[pallet::weight(1_000_000)] - pub fn prepare_new_query(origin: OriginFor) -> DispatchResult { + 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::weight(1_000_000)] - pub fn prepare_new_notify_query(origin: OriginFor) -> DispatchResult { + 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)) @@ -96,9 +105,10 @@ 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 }, ::Call::from(call), 100u32.into(), + querier, ); Self::deposit_event(Event::::NotifyQueryPrepared(qid)); Ok(()) @@ -147,23 +157,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! { @@ -216,9 +243,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: Weight = 1_000; } @@ -240,6 +267,7 @@ parameter_types! { pub CurrencyPerSecond: (AssetId, u128) = (Concrete(RelayLocation::get()), 1); pub TrustedAssets: (MultiAssetFilter, MultiLocation) = (All.into(), Here.into()); pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } pub type Barrier = ( @@ -257,20 +285,27 @@ 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 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; } pub type LocalOriginToLocation = SignedToAccountId32; parameter_types! { - pub static AdvertisedXcmVersion: pallet_xcm::XcmVersion = 2; + pub static AdvertisedXcmVersion: pallet_xcm::XcmVersion = 3; } impl pallet_xcm::Config for Test { @@ -283,11 +318,16 @@ impl pallet_xcm::Config for Test { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; 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>; } impl origin::Config for Test {} @@ -335,3 +375,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 b61f61e436bd..6eb417453784 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -24,7 +24,7 @@ use frame_support::{ }; use polkadot_parachain::primitives::{AccountIdConversion, Id as ParaId}; use sp_runtime::traits::{BlakeTwo256, Hash}; -use xcm::prelude::*; +use xcm::{latest::QueryResponseInfo, prelude::*}; use xcm_builder::AllowKnownQueryResponses; use xcm_executor::{traits::ShouldExecute, XcmExecutor}; @@ -38,7 +38,7 @@ const SEND_AMOUNT: u128 = 10; fn report_outcome_notify_works() { let balances = vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), 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(), @@ -49,35 +49,42 @@ fn report_outcome_notify_works() { }; let notify = Call::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: 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 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, - ); + let message = Xcm(vec![QueryResponse { + query_id: 0, + response: Response::ExecutionResult(None), + max_weight: 1_000_000, + querier: Some(querier), + }]); + let hash = fake_message_hash(&message); + let r = + XcmExecutor::::execute_xcm(Parachain(PARA_ID), message, hash, 1_000_000_000); assert_eq!(r, Outcome::Complete(1_000)); assert_eq!( last_events(2), @@ -98,38 +105,135 @@ fn report_outcome_notify_works() { fn report_outcome_works() { let balances = vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), 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: 0 + }),])), 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: 0, + querier: Some(querier), + }]); + let hash = fake_message_hash(&message); + let r = + XcmExecutor::::execute_xcm(Parachain(PARA_ID), message, hash, 1_000_000_000); + assert_eq!(r, Outcome::Complete(1_000)); + assert_eq!( + last_event(), + Event::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(), 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(Origin::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: 0, + querier: None, + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm_in_credit( + AccountId32 { network: None, id: ALICE.into() }, + message, + hash, + 1_000_000_000, + 1_000, + ); + assert_eq!(r, Outcome::Complete(1_000)); + assert_eq!( + last_event(), + Event::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: 0, + 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, + 1_000_000_000, + 1_000, + ); + assert_eq!(r, Outcome::Complete(1_000)); + assert_eq!( + last_event(), + Event::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: 0, + 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, - }]), + AccountId32 { network: None, id: ALICE.into() }, + message, + hash, 1_000_000_000, ); assert_eq!(r, Outcome::Complete(1_000)); @@ -151,13 +255,12 @@ fn send_works() { let balances = vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), 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())); @@ -189,11 +292,11 @@ fn send_fails_when_xcm_router_blocks() { vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)]; 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( @@ -217,7 +320,7 @@ fn teleport_assets_works() { new_test_ext_with_balances(balances).execute_with(|| { let weight = 2 * BaseXcmWeight::get(); 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( Origin::signed(ALICE), Box::new(RelayLocation::get().into()), @@ -234,12 +337,12 @@ fn teleport_assets_works() { ReceiveTeleportedAsset((Here, SEND_AMOUNT).into()), ClearOrigin, buy_limited_execution((Here, SEND_AMOUNT), 4000), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, + 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(), Event::XcmPallet(crate::Event::Attempted(Outcome::Complete(weight))) @@ -258,7 +361,7 @@ fn limmited_teleport_assets_works() { new_test_ext_with_balances(balances).execute_with(|| { let weight = 2 * BaseXcmWeight::get(); 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( Origin::signed(ALICE), Box::new(RelayLocation::get().into()), @@ -276,12 +379,12 @@ fn limmited_teleport_assets_works() { ReceiveTeleportedAsset((Here, SEND_AMOUNT).into()), ClearOrigin, buy_limited_execution((Here, SEND_AMOUNT), 5000), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, + 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(), Event::XcmPallet(crate::Event::Attempted(Outcome::Complete(weight))) @@ -300,7 +403,7 @@ fn unlimmited_teleport_assets_works() { new_test_ext_with_balances(balances).execute_with(|| { let weight = 2 * BaseXcmWeight::get(); 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( Origin::signed(ALICE), Box::new(RelayLocation::get().into()), @@ -318,7 +421,7 @@ fn unlimmited_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 }, ]), )] ); @@ -339,12 +442,11 @@ fn reserve_transfer_assets_works() { vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)]; 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( Origin::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, @@ -362,12 +464,12 @@ fn reserve_transfer_assets_works() { ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), ClearOrigin, buy_limited_execution((Parent, SEND_AMOUNT), 4000), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, + 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(), Event::XcmPallet(crate::Event::Attempted(Outcome::Complete(weight))) @@ -385,12 +487,11 @@ fn limited_reserve_transfer_assets_works() { vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)]; 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( Origin::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, @@ -409,12 +510,12 @@ fn limited_reserve_transfer_assets_works() { ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), ClearOrigin, buy_limited_execution((Parent, SEND_AMOUNT), 5000), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, + 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(), Event::XcmPallet(crate::Event::Attempted(Outcome::Complete(weight))) @@ -432,12 +533,11 @@ fn unlimited_reserve_transfer_assets_works() { vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)]; 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( Origin::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, @@ -456,7 +556,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 }, ]), )] ); @@ -477,15 +577,14 @@ fn execute_withdraw_to_deposit_works() { vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), 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 dest: MultiLocation = Junction::AccountId32 { network: None, id: BOB.into() }.into(); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); assert_ok!(XcmPallet::execute( Origin::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 )); @@ -504,8 +603,7 @@ 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 dest: MultiLocation = Junction::AccountId32 { network: None, id: BOB.into() }.into(); assert_ok!(XcmPallet::execute( Origin::signed(ALICE), @@ -517,12 +615,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())); @@ -547,7 +645,7 @@ fn trapped_assets_can_be_claimed() { 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 )); @@ -562,7 +660,7 @@ fn trapped_assets_can_be_claimed() { 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 )); @@ -579,15 +677,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( Origin::root(), Box::new(remote.clone().into()), @@ -602,7 +700,7 @@ fn basic_subscription_works() { ); assert_eq!( VersionNotifiers::::iter().collect::>(), - vec![(2, remote.clone().into(), 0)] + vec![(XCM_VERSION, remote.clone().into(), 0)] ); assert_eq!( @@ -615,12 +713,17 @@ fn basic_subscription_works() { 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: 0, + response: Response::Version(1), + querier: None, + }, ]); assert_ok!(AllowKnownQueryResponses::::should_execute( &remote, - &mut message, + message.inner_mut(), weight, &mut 0 )); @@ -630,13 +733,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( Origin::root(), Box::new(remote.clone().into()), )); - let remote2 = Parachain(1001).into(); + let remote2: MultiLocation = Parachain(1001).into(); assert_ok!(XcmPallet::force_subscribe_version_notify( Origin::root(), Box::new(remote2.clone().into()), @@ -661,7 +764,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( Origin::root(), Box::new(remote.clone().into()), @@ -679,7 +782,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( Origin::root(), Box::new(remote.clone().into()), @@ -715,13 +818,19 @@ 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 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: 0, + 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. @@ -735,7 +844,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: 0, + response: Response::Version(2), + querier: None, + }; assert_eq!(take_sent_xcm(), vec![(remote.clone(), Xcm(vec![instr]))]); }); } @@ -745,25 +859,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, 0, 2)); + let v3_location = Parachain(1003).into_versioned(); + VersionNotifyTargets::::insert(3, v3_location, (72, 0, 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: 0, + response: Response::Version(3), + querier: None, + }; + let instr3 = QueryResponse { + query_id: 72, + max_weight: 0, + 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, @@ -772,9 +892,8 @@ 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])), ] ); @@ -783,9 +902,8 @@ fn subscription_side_upgrades_work_with_notify() { 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, 0, 3)), + (XCM_VERSION, Parachain(1003).into_versioned(), (72, 0, 3)), ] ); }); @@ -794,14 +912,11 @@ 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, 0, 2)); + let v3_location = Parachain(1003).into_versioned(); + VersionNotifyTargets::::insert(3, v3_location, (72, 0, 2)); // A runtime upgrade which alters the version does send notifications. XcmPallet::on_runtime_upgrade(); @@ -812,9 +927,8 @@ fn subscription_side_upgrades_work_without_notify() { 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, 0, 3)), + (XCM_VERSION, Parachain(1003).into_versioned(), (72, 0, 3)), ] ); }); @@ -823,7 +937,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( Origin::root(), Box::new(remote.clone().into()), @@ -834,22 +948,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: 0, + 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: 0, + 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. @@ -864,77 +990,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(Origin::root(), Some(1))); + assert_ok!(XcmPallet::force_default_xcm_version(Origin::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(), + remote_v3.clone(), Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: 0 }]), )] ); - // 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: 0, + 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(), + remote_v2.clone(), Xcm(vec![SubscribeVersion { query_id: 1, max_response_weight: 0 }]), )] ); - // 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: 0, + 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(())); }) } @@ -944,16 +1092,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(); + let v2_location = VersionedMultiLocation::V2(xcm::v2::Junction::Parachain(1001).into()); + VersionNotifyTargets::::insert(1, v2_location, (70, 0, 1)); + let v2_location = VersionedMultiLocation::V2(xcm::v2::Junction::Parachain(1002).into()); VersionNotifyTargets::::insert(2, v2_location, (71, 0, 1)); + let v3_location = Parachain(1003).into_versioned(); + VersionNotifyTargets::::insert(3, v3_location, (72, 0, 1)); // New version. - AdvertisedXcmVersion::set(2); + AdvertisedXcmVersion::set(3); // A runtime upgrade which alters the version does send notifications. XcmPallet::on_runtime_upgrade(); @@ -966,9 +1113,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: 0, + response: Response::Version(3), + querier: None, + }; + let instr2 = QueryResponse { + query_id: 71, + max_weight: 0, + response: Response::Version(3), + querier: None, + }; + let instr3 = QueryResponse { + query_id: 72, + max_weight: 0, + 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, @@ -977,9 +1139,9 @@ 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])), ] ); @@ -988,9 +1150,9 @@ fn subscription_side_upgrades_work_with_multistage_notify() { 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, 0, 3)), + (XCM_VERSION, Parachain(1002).into_versioned(), (71, 0, 3)), + (XCM_VERSION, Parachain(1003).into_versioned(), (72, 0, 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 37157f2c3499..5e3a0a6fe6dd 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(Call))] pub enum VersionedXcm { - V0(v0::Xcm), - V1(v1::Xcm), + #[codec(index = 1)] V2(v2::Xcm), + #[codec(index = 2)] + 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 { 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 { +pub struct AlwaysV3; +impl WrapVersion for AlwaysV3 { fn wrap_version( _: &latest::MultiLocation, xcm: impl Into>, ) -> Result, ()> { - Ok(VersionedXcm::::V2(xcm.into().try_into()?)) + 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 0c559ca5a136..000000000000 --- a/xcm/src/v0/junction.rs +++ /dev/null @@ -1,205 +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 alloc::vec::Vec; -use parity_scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; - -/// A global identifier of an account-bearing consensus system. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo)] -pub enum NetworkId { - /// Unidentified/any. - Any, - /// Some named network. - Named(Vec), - /// The Polkadot Relay chain - Polkadot, - /// Kusama. - Kusama, -} - -/// An identifier of a pluralistic body. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo)] -pub enum BodyId { - /// The only body in its context. - Unit, - /// A named body. - Named(Vec), - /// 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, -} - -/// A part of a pluralistic body. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo)] -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)] -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(Vec), - /// 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 08db57c2e061..000000000000 --- a/xcm/src/v0/mod.rs +++ /dev/null @@ -1,385 +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(Call))] -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 0491e27d644f..000000000000 --- a/xcm/src/v0/multi_location.rs +++ /dev/null @@ -1,714 +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() { - return None - } - for i in 0..prefix.len() { - if prefix.at(i) != self.at(i) { - return None - } - } - return self.at(prefix.len()) - } - - /// 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 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 c55a61f6ffe9..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(Call))] -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 cfbc6a2e6a8f..000000000000 --- a/xcm/src/v0/traits.rs +++ /dev/null @@ -1,263 +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 83b1124904e5..000000000000 --- a/xcm/src/v1/mod.rs +++ /dev/null @@ -1,514 +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(Call))] -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 f97e56f653f4..000000000000 --- a/xcm/src/v1/order.rs +++ /dev/null @@ -1,290 +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(Call))] -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 33b60455b0b2..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 80% rename from xcm/src/v1/junction.rs rename to xcm/src/v2/junction.rs index 6e0678f76c26..c441981ec381 100644 --- a/xcm/src/v1/junction.rs +++ b/xcm/src/v2/junction.rs @@ -17,7 +17,7 @@ //! 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 alloc::vec::Vec; use parity_scale_codec::{self, Decode, Encode}; use scale_info::TypeInfo; @@ -26,6 +26,7 @@ use scale_info::TypeInfo; /// /// 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)] +#[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,25 @@ 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()), + 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 94e9ddf3817d..ba0a2dfc6b3d 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -50,23 +50,179 @@ //! `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, 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 scale_info::TypeInfo; +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)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum NetworkId { + /// Unidentified/any. + Any, + /// Some named network. + Named(Vec), + /// 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)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum BodyId { + /// The only body in its context. + Unit, + /// A named body. + Named(Vec), + /// 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, +} + +impl From for BodyId { + fn from(n: NewBodyId) -> Self { + use NewBodyId::*; + match n { + Unit => Self::Unit, + Moniker(n) => Self::Named(n[..].to_vec()), + Index(n) => Self::Index(n), + Executive => Self::Executive, + Technical => Self::Technical, + Legislative => Self::Legislative, + Judicial => Self::Judicial, + } + } +} + +/// A part of a pluralistic body. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo)] +#[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; @@ -422,6 +578,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 +795,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 +813,8 @@ pub enum Instruction { /// Cancel the effect of a previous `SubscribeVersion` instruction. /// /// Kind: *Instruction* + /// + /// Errors: *Fallible* UnsubscribeVersion, } @@ -764,180 +935,124 @@ 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 }, + 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, + 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, + }, + 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, + }, + BuyExecution { fees, weight_limit } => { + let fees = fees.try_into()?; + Self::BuyExecution { fees, weight_limit } }, - 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) } + 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 }, + 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 92% rename from xcm/src/v1/multilocation.rs rename to xcm/src/v2/multilocation.rs index 073d37ba48ee..95dbd636a93c 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}; 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)] +#[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!( @@ -253,13 +255,31 @@ impl MultiLocation { self.interior.match_and_split(&prefix.interior) } + /// Returns whether `self` has the same number of parents as `prefix` and its junctions begins + /// with the junctions of `prefix`. + /// + /// # Example + /// ```rust + /// # 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))))); + /// assert!(!m.starts_with(&MultiLocation::new(0, X1(PalletInstance(3))))); + /// ``` + pub fn starts_with(&self, prefix: &MultiLocation) -> bool { + if self.parents != prefix.parents { + return false + } + 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::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(())); @@ -282,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(())); @@ -324,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`. /// @@ -379,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; @@ -390,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 } @@ -399,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 } @@ -408,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; @@ -426,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)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum Junctions { /// The interpreting consensus system. Here, @@ -793,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)); @@ -801,15 +844,29 @@ impl Junctions { /// # } /// ``` pub fn match_and_split(&self, prefix: &Junctions) -> Option<&Junction> { - if prefix.len() + 1 != self.len() { + if prefix.len() + 1 != self.len() || !self.starts_with(prefix) { return None } - for i in 0..prefix.len() { - if prefix.at(i) != self.at(i) { - return None - } + self.at(prefix.len()) + } + + /// Returns whether `self` begins with or is equal to `prefix`. + /// + /// # Example + /// ```rust + /// # 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)); + /// assert!(j.starts_with(&X1(Parachain(2)))); + /// assert!(!j.starts_with(&X1(Parachain(999)))); + /// assert!(!j.starts_with(&X4(Parachain(2), PalletInstance(3), OnlyChild, OnlyChild))); + /// ``` + pub fn starts_with(&self, prefix: &Junctions) -> bool { + if self.len() < prefix.len() { + return false } - return self.at(prefix.len()) + prefix.iter().zip(self.iter()).all(|(l, r)| l == r) } } @@ -827,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] @@ -1010,8 +1067,6 @@ mod tests { #[test] fn conversion_from_other_types_works() { - use crate::v0; - fn takes_multilocation>(_arg: Arg) {} takes_multilocation(Parent); @@ -1028,24 +1083,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(), - Ok(MultiLocation { parents: 2, interior: X1(GeneralKey(b"foo".to_vec())) }), - ); } } diff --git a/xcm/src/v2/traits.rs b/xcm/src/v2/traits.rs index 059def0b7259..af0240c1ad26 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, + MultiLocationFull => Self::MultiLocationFull, + MultiLocationNotInvertible => 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..17200f985de2 --- /dev/null +++ b/xcm/src/v3/junction.rs @@ -0,0 +1,328 @@ +// 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, + /// The Ethereum network, including hard-forks supported by the Etheruem Foundation. + EthereumFoundation, + /// The Ethereum network, including hard-forks supported by Ethereum Classic developers. + EthereumClassic, + /// 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, +} + +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, + }) + } +} + +/// 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..34815add7fb2 --- /dev/null +++ b/xcm/src/v3/junctions.rs @@ -0,0 +1,678 @@ +// 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(ref network)) = self.first() { + Ok(network.clone()) + } else { + Err(()) + } + } + + /// 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<'a>(&'a mut self, i: usize) -> Option<&'a 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..cd4060566aac --- /dev/null +++ b/xcm/src/v3/mod.rs @@ -0,0 +1,1373 @@ +// 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, 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, WeightLimit}; + +/// 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 * 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. + #[codec(compact)] + pub max_weight: Weight, +} + +/// 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, + #[codec(compact)] + 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, + #[codec(compact)] + 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* + 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, + #[codec(compact)] + 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 `NoteAssetLocked` 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), +} + +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), + } + } +} + +// 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), + } + } +} + +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::>()?)) + } +} + +// 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, + 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, + call: call.into(), + }, + ReportError { query_id, dest, max_response_weight } => { + let response_info = QueryResponseInfo { + destination: dest.try_into()?, + query_id, + max_weight: max_response_weight, + }; + 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: max_response_weight, + }; + Self::ReportHolding { response_info, assets: assets.try_into()? } + }, + BuyExecution { fees, weight_limit } => + Self::BuyExecution { fees: fees.try_into()?, 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 }, + 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(1).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..bbd26b9e2dcd --- /dev/null +++ b/xcm/src/v3/multiasset.rs @@ -0,0 +1,871 @@ +// 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 { + Fungible(#[codec(compact)] u128), + 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 { + 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 { + Concrete(MultiLocation), + 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]; + let (left, _right) = r.split_at_mut(v.len()); + left.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.clone()).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 } + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct MultiAsset { + pub id: AssetId, + 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`. + 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 may be no duplicate fungible items in here and when decoding, they must be sorted. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, TypeInfo, Default)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +// TODO: Change to a `BoundedVec`. +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) { + if let Fungibility::Fungible(ref amount) = a.fun { + for asset in self.0.iter_mut().filter(|x| x.id == a.id) { + if let Fungibility::Fungible(ref mut balance) = asset.fun { + *balance = balance.saturating_add(*amount); + 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`. + 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.clone())) + } + + /// 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`. + /// + /// 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 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, either `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 { + Definite(MultiAssets), + 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(()), + }) + } +} + +#[test] +fn conversion_works() { + use super::prelude::*; + + let _: MultiAssets = (Here, 1u128).into(); +} 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..6f78cb19ef67 --- /dev/null +++ b/xcm/src/v3/traits.rs @@ -0,0 +1,531 @@ +// 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; + +use super::*; + +#[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)] + MultiLocationFull, + /// `MultiLocation` value ascend more parents than known ancestors of local location. + #[codec(index = 5)] + MultiLocationNotInvertible, + /// 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, + + // 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, +} + +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::MultiLocationFull, + MultiLocationNotInvertible => 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::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>; + +/// 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, + } + } +} + +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, 0) + } + + /// 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 > 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. +/// +/// 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::*; +/// # 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: 0, +/// 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 { + 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.clone()))?; + let hash = T::deliver(ticket)?; + Ok((hash, price)) +} diff --git a/xcm/xcm-builder/Cargo.toml b/xcm/xcm-builder/Cargo.toml index 6cec7193acfd..884f5530075e 100644 --- a/xcm/xcm-builder/Cargo.toml +++ b/xcm/xcm-builder/Cargo.toml @@ -6,6 +6,7 @@ description = "Tools & types for building with XCM and its executor." version = "0.9.19" [dependencies] +impl-trait-for-tuples = "0.2.1" parity-scale-codec = { version = "3.1.2", default-features = false, features = ["derive"] } scale-info = { version = "2.1.1", 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 = [] @@ -41,6 +44,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 ef7333e6d140..ed8512342d1b 100644 --- a/xcm/xcm-builder/src/barriers.rs +++ b/xcm/xcm-builder/src/barriers.rs @@ -16,11 +16,21 @@ //! Various implementations for `ShouldExecute`. -use frame_support::{ensure, traits::Contains, weights::Weight}; +use frame_support::{ + ensure, + traits::{Contains, Get}, + weights::Weight, +}; use polkadot_parachain::primitives::IsSystem; use sp_std::{marker::PhantomData, result::Result}; -use xcm::latest::{Instruction::*, Junction, Junctions, MultiLocation, WeightLimit::*, Xcm}; -use xcm_executor::traits::{OnResponse, ShouldExecute}; +use xcm::latest::{ + Instruction::{self, *}, + InteriorMultiLocation, Junction, Junctions, + Junctions::X1, + MultiLocation, + WeightLimit::*, +}; +use xcm_executor::traits::{CheckSuspension, OnResponse, RejectReason, ShouldExecute}; /// Execution barrier that just takes `max_weight` from `weight_credit`. /// @@ -31,16 +41,17 @@ 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<(), ()> { + ) -> Result<(), RejectReason> { 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(RejectReason::InsufficientCredit)?; Ok(()) } } @@ -54,59 +65,183 @@ 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<(), ()> { + ) -> Result<(), RejectReason> { 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(); - let i = iter.next().ok_or(())?; + + ensure!(T::contains(origin), RejectReason::UntrustedOrigin); + let mut iter = instructions.iter_mut(); + let i = iter.next().ok_or(RejectReason::UnexpectedMessageFormat)?; match i { ReceiveTeleportedAsset(..) | WithdrawAsset(..) | ReserveAssetDeposited(..) | ClaimAsset { .. } => (), - _ => return Err(()), + _ => return Err(RejectReason::UnexpectedMessageFormat), } - let mut i = iter.next().ok_or(())?; + let mut i = iter.next().ok_or(RejectReason::UnexpectedMessageFormat)?; while let ClearOrigin = i { - i = iter.next().ok_or(())?; + i = iter.next().ok_or(RejectReason::UnexpectedMessageFormat)?; } match i { - BuyExecution { weight_limit: Limited(ref mut weight), .. } if *weight >= max_weight => { - *weight = max_weight; - Ok(()) - }, + BuyExecution { weight_limit: Limited(ref mut weight), .. } => + if *weight >= max_weight { + *weight = max_weight; + Ok(()) + } else { + Err(RejectReason::WeightLimitTooLow) + }, BuyExecution { ref mut weight_limit, .. } if weight_limit == &Unlimited => { *weight_limit = Limited(max_weight); Ok(()) }, - _ => Err(()), + _ => Err(RejectReason::UnexpectedMessageFormat), + } + } +} + +/// 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<(), RejectReason> { + log::trace!( + target: "xcm::barriers", + "WithComputedOrigin origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}", + origin, instructions, max_weight, weight_credit, + ); + let mut actual_origin = origin.clone(); + 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 technically 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.clone()).relative_to(&LocalUniversal::get()); + }, + Some(DescendOrigin(j)) => { + actual_origin + .append_with(j.clone()) + .map_err(|_| RejectReason::OriginMultiLocationTooLong)?; + }, + _ => 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)`) without any payments. +/// Barrier condition that allows for a `SuspensionChecker` that controls whether or not the XCM +/// executor will be suspended from executing the given XCM. +pub struct RespectSuspension(PhantomData<(Inner, SuspensionChecker)>); +impl ShouldExecute for RespectSuspension +where + Inner: ShouldExecute, + SuspensionChecker: CheckSuspension, +{ + fn should_execute( + origin: &MultiLocation, + instructions: &mut [Instruction], + max_weight: Weight, + weight_credit: &mut Weight, + ) -> Result<(), RejectReason> { + if SuspensionChecker::is_suspended(origin, instructions, max_weight, weight_credit) { + Err(RejectReason::Suspended) + } else { + Inner::should_execute(origin, instructions, max_weight, weight_credit) + } + } +} + +/// 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. 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<(), ()> { + ) -> Result<(), RejectReason> { 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), ()); + ensure!(T::contains(origin), RejectReason::UntrustedOrigin); Ok(()) } } @@ -128,20 +263,24 @@ 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<(), ()> { + ) -> Result<(), RejectReason> { 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) => - Ok(()), - _ => Err(()), + match instructions.first() { + Some(QueryResponse { query_id, querier, .. }) => { + if ResponseHandler::expecting_response(origin, *query_id, querier.as_ref()) { + Ok(()) + } else { + Err(RejectReason::UnexpectedResponse) + } + }, + _ => Err(RejectReason::UnexpectedMessageFormat), } } } @@ -152,19 +291,19 @@ 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<(), ()> { + ) -> Result<(), RejectReason> { 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()) { + ensure!(T::contains(origin), RejectReason::UntrustedOrigin); + match (instructions.len(), instructions.first()) { (1, Some(SubscribeVersion { .. })) | (1, Some(UnsubscribeVersion)) => Ok(()), - _ => Err(()), + _ => Err(RejectReason::UnexpectedMessageFormat), } } } diff --git a/xcm/xcm-builder/src/currency_adapter.rs b/xcm/xcm-builder/src/currency_adapter.rs index dfdfd4c6964a..681607b7b038 100644 --- a/xcm/xcm-builder/src/currency_adapter.rs +++ b/xcm/xcm-builder/src/currency_adapter.rs @@ -19,7 +19,7 @@ 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 +67,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,15 +89,15 @@ pub struct CurrencyAdapter, Matcher: MatchesFungible, AccountIdConverter: Convert, - Currency: frame_support::traits::Currency, AccountId: Clone, // can't get away without it since Currency is generic over it. 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 = @@ -114,7 +117,7 @@ impl< 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() { @@ -133,7 +136,7 @@ impl< } } - fn check_out(_dest: &MultiLocation, what: &MultiAsset) { + 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() { @@ -142,7 +145,7 @@ impl< } } - 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)?; @@ -152,7 +155,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)?; @@ -167,6 +174,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..3a96c417177e 100644 --- a/xcm/xcm-builder/src/fungibles_adapter.rs +++ b/xcm/xcm-builder/src/fungibles_adapter.rs @@ -17,97 +17,10 @@ //! 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)) - } -} - pub struct FungiblesTransferAdapter( PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>, ); @@ -122,6 +35,7 @@ impl< what: &MultiAsset, from: &MultiLocation, to: &MultiLocation, + _context: &XcmContext, ) -> result::Result { log::trace!( target: "xcm::fungibles_adapter", @@ -165,7 +79,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: {:?}", @@ -183,7 +101,7 @@ impl< 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: {:?}", @@ -201,7 +119,7 @@ impl< } } - fn check_out(_dest: &MultiLocation, what: &MultiAsset) { + fn check_out(_dest: &MultiLocation, what: &MultiAsset, _context: &XcmContext) { log::trace!( target: "xcm::fungibles_adapter", "check_out dest: {:?}, what: {:?}", @@ -216,7 +134,7 @@ impl< } } - 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 +151,7 @@ impl< fn withdraw_asset( what: &MultiAsset, who: &MultiLocation, + _maybe_context: Option<&XcmContext>, ) -> result::Result { log::trace!( target: "xcm::fungibles_adapter", @@ -267,7 +186,7 @@ impl< > 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, @@ -275,10 +194,10 @@ impl< AccountId, CheckAsset, CheckingAccount, - >::can_check_in(origin, what) + >::can_check_in(origin, what, context) } - fn check_in(origin: &MultiLocation, what: &MultiAsset) { + fn check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) { FungiblesMutateAdapter::< Assets, Matcher, @@ -286,10 +205,10 @@ impl< AccountId, CheckAsset, CheckingAccount, - >::check_in(origin, what) + >::check_in(origin, what, context) } - fn check_out(dest: &MultiLocation, what: &MultiAsset) { + fn check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) { FungiblesMutateAdapter::< Assets, Matcher, @@ -297,10 +216,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 +227,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 +242,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 e3473d55d5eb..38344b9a105c 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,30 +40,42 @@ 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, + AllowUnpaidExecutionFrom, IsChildSystemParachain, RespectSuspension, TakeWeightCredit, + WithComputedOrigin, }; mod currency_adapter; pub use currency_adapter::CurrencyAdapter; mod fungibles_adapter; -pub use fungibles_adapter::{ - AsPrefixedGeneralIndex, ConvertedAbstractAssetId, ConvertedConcreteAssetId, FungiblesAdapter, - FungiblesMutateAdapter, FungiblesTransferAdapter, +pub use fungibles_adapter::{FungiblesAdapter, FungiblesMutateAdapter, FungiblesTransferAdapter}; + +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::{ + ExporterFor, LocalUnpaidExporter, NetworkExportTable, SovereignPaidRemoteExporter, + UnpaidRemoteExporter, +}; diff --git a/xcm/xcm-builder/src/location_conversion.rs b/xcm/xcm-builder/src/location_conversion.rs index a5bb40c87ec0..cc5770b3cca9 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..391eb061fa10 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,16 @@ 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(ref id), NonFungible(ref instance)) if id == &T::get() => + instance.clone().try_into().ok(), _ => None, } } @@ -64,24 +73,38 @@ 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(ref id), NonFungible(ref instance)) if id == &T::get() => + instance.clone().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 131d09f71eec..000000000000 --- a/xcm/xcm-builder/src/mock.rs +++ /dev/null @@ -1,291 +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, Parameter, Weight, - }, - ensure, parameter_types, - sp_runtime::DispatchErrorWithPostInfo, - traits::{Contains, Get, IsInVec}, - weights::{GetDispatchInfo, PostDispatchInfo}, -}; -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::*; -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 Origin = TestOrigin; - type Config = (); - type Info = (); - type PostInfo = PostDispatchInfo; - fn dispatch(self, origin: Self::Origin) -> DispatchResultWithPostInfo { - let mut post_info = PostDispatchInfo::default(); - post_info.actual_weight = match self { - TestCall::OnlyRoot(_, maybe_actual) | - TestCall::OnlySigned(_, maybe_actual, _) | - TestCall::OnlyParachain(_, maybe_actual, _) | - TestCall::Any(_, maybe_actual) => 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> = 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: Weight = 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 Call = 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..d717145cc4fc --- /dev/null +++ b/xcm/xcm-builder/src/nonfungibles_adapter.rs @@ -0,0 +1,256 @@ +// 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::{ + ensure, + traits::{tokens::nonfungibles, Contains, 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: Contains, + 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)?; + if CheckAsset::contains(&class) { + 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 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) { + if CheckAsset::contains(&class) { + let ok = Assets::burn(&class, &instance, None).is_ok(); + debug_assert!( + ok, + "`can_check_in` must have returned `true` immediately prior; qed" + ); + } + } + } + + 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) { + if CheckAsset::contains(&class) { + 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 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: Contains, + 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 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 4d9a882464d8..e24c426e892f 100644 --- a/xcm/xcm-builder/src/origin_conversion.rs +++ b/xcm/xcm-builder/src/origin_conversion.rs @@ -177,7 +177,7 @@ impl, Origin> ConvertOrigin } pub struct SignedAccountId32AsNative(PhantomData<(Network, Origin)>); -impl, Origin: OriginTrait> ConvertOrigin +impl>, Origin: OriginTrait> ConvertOrigin for SignedAccountId32AsNative where Origin::AccountId: From<[u8; 32]>, @@ -196,15 +196,14 @@ where ( OriginKind::Native, MultiLocation { parents: 0, interior: X1(Junction::AccountId32 { id, network }) }, - ) if matches!(network, NetworkId::Any) || network == Network::get() => - Ok(Origin::signed(id.into())), + ) if matches!(network, None) || network == Network::get() => Ok(Origin::signed(id.into())), (_, origin) => Err(origin), } } } pub struct SignedAccountKey20AsNative(PhantomData<(Network, Origin)>); -impl, Origin: OriginTrait> ConvertOrigin +impl>, Origin: OriginTrait> ConvertOrigin for SignedAccountKey20AsNative where Origin::AccountId: From<[u8; 20]>, @@ -223,8 +222,7 @@ where ( OriginKind::Native, MultiLocation { parents: 0, interior: X1(Junction::AccountKey20 { key, network }) }, - ) if (matches!(network, NetworkId::Any) || network == Network::get()) => - Ok(Origin::signed(key.into())), + ) if (matches!(network, None) || network == Network::get()) => Ok(Origin::signed(key.into())), (_, origin) => Err(origin), } } @@ -265,7 +263,7 @@ where pub struct SignedToAccountId32( PhantomData<(Origin, AccountId, Network)>, ); -impl, Network: Get> +impl, Network: Get>> Convert for SignedToAccountId32 where Origin::PalletsOrigin: From> diff --git a/xcm/xcm-builder/src/test_utils.rs b/xcm/xcm-builder/src/test_utils.rs index 30c436f46775..6376e46110e2 100644 --- a/xcm/xcm-builder/src/test_utils.rs +++ b/xcm/xcm-builder/src/test_utils.rs @@ -16,29 +16,41 @@ // Shared test utilities and implementations for the XCM Builder. -use frame_support::{dispatch::Weight, parameter_types}; +use frame_support::{ + dispatch::Weight, + parameter_types, + traits::{Contains, CrateVersion, PalletInfoData, PalletsInfoAccess}, +}; use sp_std::vec::Vec; pub use xcm::latest::prelude::*; 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 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: u64, + _context: &XcmContext, + ) -> XcmResult { let mut r = SubscriptionRequests::get(); r.push((location.clone(), 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)); @@ -58,7 +70,7 @@ 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())); TrappedAssets::set(t); @@ -67,7 +79,12 @@ impl DropAssets for TestAssetTrap { } 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 +98,118 @@ 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 accumulate(acc: &mut Vec) { + acc.push(PalletInfoData { + index: 0, + name: "System", + module_name: "pallet_system", + crate_version: CrateVersion { major: 1, minor: 10, patch: 1 }, + }); + acc.push(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 2b154f4f00c2..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_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_PER_SECOND.into()); - pub static PARA1WeightPrice: (AssetId, u128) = (PARA_1.into(), WEIGHT_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..e78047988e04 --- /dev/null +++ b/xcm/xcm-builder/src/tests/assets.rs @@ -0,0 +1,391 @@ +// 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, 50); + assert_eq!(r, Outcome::Complete(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, 50); + assert_eq!(r, Outcome::Complete(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, 50); + assert_eq!(r, Outcome::Incomplete(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)); + + let fees = (Parent, 30u128).into(); + let message = Xcm(vec![ + ReserveAssetDeposited((Parent, 100u128).into()), + BuyExecution { fees, weight_limit: Limited(30) }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() }, + ]); + let hash = fake_message_hash(&message); + let weight_limit = 50; + let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); + assert_eq!(r, Outcome::Complete(30)); + assert_eq!(asset_list(Here), vec![(Parent, 70u128).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, 50); + assert_eq!(r, Outcome::Complete(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, 50); + assert_eq!(r, Outcome::Complete(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, 50); + assert_eq!(r, Outcome::Complete(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, 50); + assert_eq!(r, Outcome::Complete(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, 20); + assert_eq!(r, Outcome::Complete(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, 20); + assert_eq!(r, Outcome::Incomplete(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, 20); + assert_eq!(r, Outcome::Incomplete(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, 20); + assert_eq!(r, Outcome::Incomplete(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, 20); + assert_eq!(r, Outcome::Complete(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, 20); + assert_eq!(r, Outcome::Incomplete(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, 100); + assert_eq!(r, Outcome::Complete(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, 100); + assert_eq!(r, Outcome::Incomplete(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, 200); + assert_eq!(r, Outcome::Complete(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, 200); + assert_eq!(r, Outcome::Incomplete(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, 200); + assert_eq!(r, Outcome::Incomplete(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..93cabb905f7e --- /dev/null +++ b/xcm/xcm-builder/src/tests/barriers.rs @@ -0,0 +1,189 @@ +// 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 = 10; + let r = TakeWeightCredit::should_execute( + &Parent.into(), + message.inner_mut(), + 10, + &mut weight_credit, + ); + assert_eq!(r, Ok(())); + assert_eq!(weight_credit, 0); + + let r = TakeWeightCredit::should_execute( + &Parent.into(), + message.inner_mut(), + 10, + &mut weight_credit, + ); + assert_eq!(r, Err(RejectReason::InsufficientCredit)); + assert_eq!(weight_credit, 0); +} + +#[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(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(), + 100, + &mut 0, + ); + assert_eq!(r, Err(RejectReason::UntrustedOrigin)); + + let r = WithComputedOrigin::< + AllowTopLevelPaidExecutionFrom>, + ExecutorUniversalLocation, + ConstU32<2>, + >::should_execute(&Parent.into(), message.inner_mut(), 100, &mut 0); + assert_eq!(r, Err(RejectReason::UntrustedOrigin)); + + let r = WithComputedOrigin::< + AllowTopLevelPaidExecutionFrom>, + ExecutorUniversalLocation, + ConstU32<5>, + >::should_execute(&Parent.into(), message.inner_mut(), 100, &mut 0); + 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(), + 10, + &mut 0, + ); + assert_eq!(r, Err(RejectReason::UntrustedOrigin)); + + let r = AllowUnpaidExecutionFrom::>::should_execute( + &Parent.into(), + message.inner_mut(), + 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(), + message.inner_mut(), + 10, + &mut 0, + ); + assert_eq!(r, Err(RejectReason::UntrustedOrigin)); + + let fees = (Parent, 1).into(); + let mut underpaying_message = Xcm::<()>(vec![ + ReserveAssetDeposited((Parent, 100).into()), + BuyExecution { fees, weight_limit: Limited(20) }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() }, + ]); + + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &Parent.into(), + underpaying_message.inner_mut(), + 30, + &mut 0, + ); + assert_eq!(r, Err(RejectReason::WeightLimitTooLow)); + + let fees = (Parent, 1).into(); + let mut paying_message = Xcm::<()>(vec![ + ReserveAssetDeposited((Parent, 100).into()), + BuyExecution { fees, weight_limit: Limited(30) }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() }, + ]); + + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &Parachain(1).into(), + paying_message.inner_mut(), + 30, + &mut 0, + ); + assert_eq!(r, Err(RejectReason::UntrustedOrigin)); + + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &Parent.into(), + paying_message.inner_mut(), + 30, + &mut 0, + ); + assert_eq!(r, Ok(())); +} + +#[test] +fn suspension_should_work() { + TestSuspender::set_suspended(true); + AllowUnpaidFrom::set(vec![Parent.into()]); + + let mut message = + Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]); + let r = RespectSuspension::>, TestSuspender>::should_execute( + &Parent.into(), + message.inner_mut(), + 10, + &mut 0, + ); + assert_eq!(r, Err(RejectReason::Suspended)); + + TestSuspender::set_suspended(false); + let mut message = + Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]); + let r = RespectSuspension::>, TestSuspender>::should_execute( + &Parent.into(), + message.inner_mut(), + 10, + &mut 0, + ); + 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..ea378e849873 --- /dev/null +++ b/xcm/xcm-builder/src/tests/basic.rs @@ -0,0 +1,100 @@ +// 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(30) }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() }, + ]); + 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(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, 70); + + let hash = fake_message_hash(&message); + + let r = XcmExecutor::::execute_xcm(Here, message.clone(), hash, limit); + assert_eq!(r, Outcome::Complete(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(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..396aa81d792f --- /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 = LocalUnpaidExporter, 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..7fd5de2b2719 --- /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 = LocalUnpaidExporter, 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..f145fc2d4d0c --- /dev/null +++ b/xcm/xcm-builder/src/tests/bridging/mod.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 . + +//! 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) { + BRIDGE_TRAFFIC.with(|t| t.borrow_mut().push(blob)); + } +} + +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, + d: &InteriorMultiLocation, + m: &Xcm<()>, +) -> Result { + Ok(validate_export::(n, c, d.clone(), m.clone())?.1) +} + +fn deliver( + n: NetworkId, + c: u32, + d: InteriorMultiLocation, + m: Xcm<()>, +) -> Result { + export_xcm::(n, c, 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, 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::); + // The we execute it: + let hash = fake_message_hash(&message); + let outcome = + XcmExecutor::::execute_xcm(origin, message.into(), hash, 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..86cea75e7300 --- /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, 150u128).into()))]; + // ^^^ 100 to use the bridge (export) and 50 for the remote execution weight (5 instuctions + // x 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, 150u128).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 weight units (and thus 50 units of balance). + assert_eq!(asset_list(Parachain(100)), vec![(Here, 850u128).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, 150u128).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 weight units (and thus 50 units of balance). + assert_eq!(asset_list(Parachain(100)), vec![(Here, 850u128).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..33830a721e05 --- /dev/null +++ b/xcm/xcm-builder/src/tests/expecting.rs @@ -0,0 +1,137 @@ +// 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, 50); + assert_eq!(r, Outcome::Complete(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, 50); + assert_eq!(r, Outcome::Complete(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, 50); + assert_eq!(r, Outcome::Incomplete(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, 50); + assert_eq!(r, Outcome::Incomplete(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, 50); + assert_eq!(r, Outcome::Incomplete(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, 50); + assert_eq!(r, Outcome::Incomplete(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, 50); + assert_eq!(r, Outcome::Incomplete(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, 50); + assert_eq!(r, Outcome::Incomplete(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, 50); + assert_eq!(r, Outcome::Incomplete(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, 50); + assert_eq!(r, Outcome::Incomplete(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..9f58936faa39 --- /dev/null +++ b/xcm/xcm-builder/src/tests/locking.rs @@ -0,0 +1,212 @@ +// 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, 50); + assert_eq!(r, Outcome::Complete(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, 50); + assert_eq!(r, Outcome::Complete(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, 50); + assert_eq!(r, Outcome::Complete(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, 50); + assert_eq!(r, Outcome::Incomplete(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, 50); + assert_eq!(r, Outcome::Incomplete(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, 50); + assert_eq!(r, Outcome::Complete(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, 50); + assert_eq!(r, Outcome::Complete(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, 50); + assert_eq!(r, Outcome::Incomplete(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, 50); + assert_eq!(r, Outcome::Complete(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, 50); + assert_eq!(r, Outcome::Incomplete(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..a387609dd296 --- /dev/null +++ b/xcm/xcm-builder/src/tests/mock.rs @@ -0,0 +1,638 @@ +// 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::{ + barriers::RespectSuspension, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, + AllowUnpaidExecutionFrom, FixedRateOfFungible, FixedWeightBounds, TakeWeightCredit, +}; +use frame_support::traits::ContainsPair; +pub use frame_support::{ + dispatch::{ + DispatchError, DispatchInfo, DispatchResultWithPostInfo, Dispatchable, Parameter, Weight, + }, + ensure, parameter_types, + sp_runtime::DispatchErrorWithPostInfo, + traits::{Contains, Get, IsInVec}, + weights::{GetDispatchInfo, PostDispatchInfo}, +}; +pub use parity_scale_codec::{Decode, Encode}; +pub use sp_io::hashing::blake2_256; +pub use sp_std::{ + cell::{Cell, RefCell}, + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + fmt::Debug, + marker::PhantomData, +}; +pub use xcm::prelude::*; +pub use xcm_executor::{ + traits::{ + AssetExchange, AssetLock, CheckSuspension, ConvertOrigin, Enact, ExportXcm, FeeManager, + FeeReason, LockError, OnResponse, RejectReason, 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 Origin = TestOrigin; + type Config = (); + type Info = (); + type PostInfo = PostDispatchInfo; + fn dispatch(self, origin: Self::Origin) -> DispatchResultWithPostInfo { + let mut post_info = PostDispatchInfo::default(); + post_info.actual_weight = match self { + TestCall::OnlyRoot(_, maybe_actual) | + TestCall::OnlySigned(_, maybe_actual, _) | + TestCall::OnlyParachain(_, maybe_actual, _) | + TestCall::Any(_, maybe_actual) => 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, XcmHash)>> = RefCell::new(Vec::new()); + pub static EXPORTER_OVERRIDE: RefCell) -> Result, + fn(NetworkId, u32, InteriorMultiLocation, Xcm<()>) -> Result, + )>> = RefCell::new(None); + pub static SEND_PRICE: RefCell = RefCell::new(MultiAssets::new()); + pub static SUSPENDED: Cell = Cell::new(false); +} +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, opaque::Xcm, XcmHash)> { + EXPORTED_XCM.with(|q| (*q.borrow()).clone()) +} +pub fn set_exporter_override( + price: fn(NetworkId, u32, &InteriorMultiLocation, &Xcm<()>) -> Result, + deliver: fn(NetworkId, u32, 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, Xcm<()>, XcmHash); + fn validate( + network: NetworkId, + channel: u32, + dest: &mut Option, + msg: &mut Option>, + ) -> SendResult<(NetworkId, u32, InteriorMultiLocation, Xcm<()>, XcmHash)> { + let (d, m) = (dest.take().unwrap(), msg.take().unwrap()); + let r: Result = EXPORTER_OVERRIDE.with(|e| { + if let Some((ref f, _)) = &*e.borrow() { + f(network, channel, &d, &m) + } else { + Ok(MultiAssets::new()) + } + }); + let h = fake_message_hash(&m); + match r { + Ok(price) => Ok(((network, channel, d, m, h), price)), + Err(e) => { + *dest = Some(d); + *msg = Some(m); + Err(e) + }, + } + } + fn deliver( + tuple: (NetworkId, u32, InteriorMultiLocation, Xcm<()>, XcmHash), + ) -> Result { + EXPORTER_OVERRIDE.with(|e| { + if let Some((_, ref f)) = &*e.borrow() { + let (network, channel, dest, msg, _hash) = tuple; + f(network, channel, dest, msg) + } else { + let hash = tuple.4; + 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); + } + }); + }); + 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 = 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 struct TestSuspender; +impl CheckSuspension for TestSuspender { + fn is_suspended( + _origin: &MultiLocation, + _instructions: &mut [Instruction], + _max_weight: Weight, + _weight_credit: &mut Weight, + ) -> bool { + SUSPENDED.with(|s| s.get()) + } +} + +impl TestSuspender { + pub fn set_suspended(suspended: bool) { + SUSPENDED.with(|s| s.set(suspended)); + } +} + +pub type TestBarrier = ( + TakeWeightCredit, + AllowKnownQueryResponses, + AllowTopLevelPaidExecutionFrom>, + 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 Call = TestCall; + type XcmSender = TestMessageSender; + type AssetTransactor = TestAssetTransactor; + type OriginConverter = TestOriginConverter; + type IsReserve = TestIsReserve; + type IsTeleporter = TestIsTeleporter; + type UniversalLocation = ExecutorUniversalLocation; + type Barrier = RespectSuspension; + 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; +} + +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 58% rename from xcm/procedural/src/v0.rs rename to xcm/xcm-builder/src/tests/mod.rs index 7774df4e9f8f..d49b207f73b1 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_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..9b75c4f98650 --- /dev/null +++ b/xcm/xcm-builder/src/tests/origins.rs @@ -0,0 +1,75 @@ +// 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, 50); + assert_eq!(r, Outcome::Incomplete(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, 50); + assert_eq!(r, Outcome::Incomplete(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, 50); + assert_eq!(r, Outcome::Complete(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, 50); + assert_eq!(r, Outcome::Complete(10)); + assert_eq!(exported_xcm(), vec![(Polkadot, 403611790, Here, expected_message, expected_hash)]); +} diff --git a/xcm/xcm-builder/src/tests/querying.rs b/xcm/xcm-builder/src/tests/querying.rs new file mode 100644 index 000000000000..43b3235cbaca --- /dev/null +++ b/xcm/xcm-builder/src/tests/querying.rs @@ -0,0 +1,109 @@ +// 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: 50, + }, + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm(Parachain(1), message, hash, 50); + assert_eq!(r, Outcome::Complete(10)); + + let expected_msg = Xcm::<()>(vec![QueryResponse { + query_id: 1, + max_weight: 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: 50, + }, + }]); + let hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm(Parachain(1), message, hash, 50); + assert_eq!(r, Outcome::Complete(10)); + + let expected_msg = Xcm::<()>(vec![QueryResponse { + query_id: 1, + max_weight: 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: 10, + querier: Some(Here.into()), + }]); + let hash = fake_message_hash(&message); + let weight_limit = 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(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..4528cef2202b --- /dev/null +++ b/xcm/xcm-builder/src/tests/transacting.rs @@ -0,0 +1,184 @@ +// 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: 50, + call: TestCall::Any(50, None).encode().into(), + }]); + let hash = fake_message_hash(&message); + let weight_limit = 60; + let r = XcmExecutor::::execute_xcm(Parent, message, hash, 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_kind: OriginKind::Native, + require_weight_at_most: 40, + call: TestCall::Any(50, None).encode().into(), + }]); + let hash = fake_message_hash(&message); + let weight_limit = 60; + let r = XcmExecutor::::execute_xcm(Parent, message, hash, 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_kind: OriginKind::Native, + require_weight_at_most: 50, + call: TestCall::Any(50, Some(30)).encode().into(), + }]); + let hash = fake_message_hash(&message); + let weight_limit = 60; + let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); + assert_eq!(r, Outcome::Complete(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, 100u128)); + WeightPrice::set((Parent.into(), 1_000_000_000_000)); + + let origin = one.clone(); + let fees = (Parent, 100u128).into(); + let message = Xcm::(vec![ + WithdrawAsset((Parent, 100u128).into()), // enough for 100 units of weight. + BuyExecution { fees, weight_limit: Limited(100) }, + Transact { + origin_kind: 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: AllCounted(1).into(), beneficiary: one.clone() }, + ]); + let hash = fake_message_hash(&message); + let weight_limit = 100; + let r = XcmExecutor::::execute_xcm(origin, message, hash, weight_limit); + assert_eq!(r, Outcome::Complete(60)); + assert_eq!( + asset_list(AccountIndex64 { index: 1, network: None }), + vec![(Parent, 40u128).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: 50, + call: TestCall::Any(50, None).encode().into(), + }, + ReportTransactStatus(QueryResponseInfo { + destination: Parent.into(), + query_id: 42, + max_weight: 5000, + }), + ]); + let hash = fake_message_hash(&message); + let weight_limit = 70; + let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); + assert_eq!(r, Outcome::Complete(70)); + let expected_msg = Xcm(vec![QueryResponse { + response: Response::DispatchResult(MaybeErrorCode::Success), + query_id: 42, + max_weight: 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: 50, + call: TestCall::OnlyRoot(50, None).encode().into(), + }, + ReportTransactStatus(QueryResponseInfo { + destination: Parent.into(), + query_id: 42, + max_weight: 5000, + }), + ]); + let hash = fake_message_hash(&message); + let weight_limit = 70; + let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); + assert_eq!(r, Outcome::Complete(70)); + let expected_msg = Xcm(vec![QueryResponse { + response: Response::DispatchResult(MaybeErrorCode::Error(vec![2])), + query_id: 42, + max_weight: 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: 50, + call: TestCall::OnlyRoot(50, None).encode().into(), + }, + ClearTransactStatus, + ReportTransactStatus(QueryResponseInfo { + destination: Parent.into(), + query_id: 42, + max_weight: 5000, + }), + ]); + let hash = fake_message_hash(&message); + let weight_limit = 80; + let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); + assert_eq!(r, Outcome::Complete(80)); + let expected_msg = Xcm(vec![QueryResponse { + response: Response::DispatchResult(MaybeErrorCode::Success), + query_id: 42, + max_weight: 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..e7125e6e75a5 --- /dev/null +++ b/xcm/xcm-builder/src/tests/version_subscriptions.rs @@ -0,0 +1,141 @@ +// 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: 5000 }, + ]); + let hash = fake_message_hash(&message); + let weight_limit = 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: 5000 }]); + let hash = fake_message_hash(&message); + let weight_limit = 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(10)); + + assert_eq!(SubscriptionRequests::get(), vec![(Parent.into(), Some((42, 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: 5000 }, + ]); + let hash = fake_message_hash(&message); + let weight_limit = 20; + let r = XcmExecutor::::execute_xcm_in_credit( + origin.clone(), + message, + hash, + 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 hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm_in_credit( + origin, + message, + hash, + 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); + let message = Xcm::(vec![SetAppendix(Xcm(vec![])), UnsubscribeVersion]); + let hash = fake_message_hash(&message); + let weight_limit = 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 = 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(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 = 20; + let r = XcmExecutor::::execute_xcm_in_credit( + origin.clone(), + message, + hash, + 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 hash = fake_message_hash(&message); + let r = XcmExecutor::::execute_xcm_in_credit( + origin, + message, + hash, + weight_limit, + weight_limit, + ); + assert_eq!(r, Outcome::Complete(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..c09c2420cda0 --- /dev/null +++ b/xcm/xcm-builder/src/tests/weight.rs @@ -0,0 +1,141 @@ +// 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 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, 30); + + let hash = fake_message_hash(&message); + + let r = XcmExecutor::::execute_xcm(Here, message.clone(), hash, limit); + assert_eq!(r, Outcome::Complete(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(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(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(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(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) = (Here.into(), WEIGHT_PER_SECOND.into()); + pub static Para1WeightPrice: (AssetId, u128) = (Parachain(1).into(), WEIGHT_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.clone(), 10).into()), + Ok(fungible_multi_asset(para_1.clone(), 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/universal_exports.rs b/xcm/xcm-builder/src/universal_exports.rs new file mode 100644 index 000000000000..5db871135db8 --- /dev/null +++ b/xcm/xcm-builder/src/universal_exports.rs @@ -0,0 +1,393 @@ +// 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, NetworkId, InteriorMultiLocation), MultiLocation> { + let dest = dest.into(); + let universal_local = universal_local.into(); + let (local_net, local_loc) = match universal_local.clone().split_first() { + (location, Some(GlobalConsensus(network))) => (network, location), + _ => return Err(dest), + }; + let universal_destination: InteriorMultiLocation = universal_local + .into_location() + .appended_with(dest.clone()) + .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, local_net, local_loc)) +} + +/// Implementation of `SendXcm` which uses the given `ExportXcm` implementation in order to forward +/// the message over a bridge. +/// +/// The actual message forwarded over the bridge is prepended with `UniversalOrigin` and +/// `DescendOrigin` in order to ensure that the message is executed with this Origin. +/// +/// 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 LocalUnpaidExporter( + PhantomData<(Exporter, UniversalLocation)>, +); +impl> SendXcm + for LocalUnpaidExporter +{ + type Ticket = Exporter::Ticket; + + fn validate( + dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + let d = dest.take().ok_or(MissingArgument)?; + let devolved = match ensure_is_remote(UniversalLocation::get(), d) { + Ok(x) => x, + Err(d) => { + *dest = Some(d); + return Err(NotApplicable) + }, + }; + let (network, destination, local_network, local_location) = devolved; + + let inner = xcm.take().ok_or(MissingArgument)?; + let mut message: Xcm<()> = vec![UniversalOrigin(GlobalConsensus(local_network))].into(); + if local_location != Here { + message.inner_mut().push(DescendOrigin(local_location)); + } + message.inner_mut().extend(inner.into_iter()); + validate_export::(network, 0, destination, message) + } + + 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. +/// +/// 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 our Origin. +/// +/// 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.as_ref().ok_or(MissingArgument)?.clone(); + let devolved = ensure_is_remote(UniversalLocation::get(), d).map_err(|_| NotApplicable)?; + let (remote_network, remote_location, local_network, local_location) = devolved; + + // 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 mut exported: Xcm<()> = vec![UniversalOrigin(GlobalConsensus(local_network))].into(); + if local_location != Here { + exported.inner_mut().push(DescendOrigin(local_location)); + } + exported.inner_mut().extend(xcm.take().ok_or(MissingArgument)?.into_iter()); + + let (bridge, maybe_payment) = + Bridges::exporter_for(&remote_network, &remote_location, &exported) + .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: exported, + }]); + 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 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. +/// +/// 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)?.clone(); + let devolved = ensure_is_remote(UniversalLocation::get(), d).map_err(|_| NotApplicable)?; + let (remote_network, remote_location, local_network, local_location) = devolved; + + // 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 mut exported: Xcm<()> = vec![UniversalOrigin(GlobalConsensus(local_network))].into(); + if local_location != Here { + exported.inner_mut().push(DescendOrigin(local_location)); + } + exported.inner_mut().extend(xcm.take().ok_or(MissingArgument)?.into_iter()); + + let (bridge, maybe_payment) = + Bridges::exporter_for(&remote_network, &remote_location, &exported) + .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: exported }; + + 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. This will only work if the bridge will do the message + // export for free. Common-good chains will typically be afforded this. + 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); +} + +#[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<()>, +} + +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)?; + 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, + 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 message = VersionedXcm::from(message.take().ok_or(SendError::MissingArgument)?); + 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, Here))); + + // 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, Kusama, Parachain(1000).into()))); + + // 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 3d161dbf7a94..798b2fe2f665 100644 --- a/xcm/xcm-builder/src/weight.rs +++ b/xcm/xcm-builder/src/weight.rs @@ -127,62 +127,6 @@ 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_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_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 diff --git a/xcm/xcm-builder/tests/mock/mod.rs b/xcm/xcm-builder/tests/mock/mod.rs index 9599efcd7f29..343a52129f56 100644 --- a/xcm/xcm-builder/tests/mock/mod.rs +++ b/xcm/xcm-builder/tests/mock/mod.rs @@ -19,6 +19,7 @@ use frame_support::{ 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; @@ -32,7 +33,7 @@ use xcm_builder::{ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, - IsChildSystemParachain, IsConcrete, LocationInverter, SignedAccountId32AsNative, + IsChildSystemParachain, IsConcrete, RespectSuspension, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, }; @@ -40,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) } } @@ -116,7 +128,7 @@ 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 UniversalLocation: InteriorMultiLocation = Here; pub CheckAccount: AccountId = XcmPallet::check_account(); } @@ -153,9 +165,10 @@ 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,); @@ -167,21 +180,28 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = LocalOriginConverter; type IsReserve = (); type IsTeleporter = TrustedTeleporters; - type LocationInverter = LocationInverter; - type Barrier = Barrier; + type UniversalLocation = UniversalLocation; + type Barrier = RespectSuspension; type Weigher = FixedWeightBounds; 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; } pub type LocalOriginToLocation = SignedToAccountId32; impl pallet_xcm::Config for Runtime { type Event = Event; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; type XcmRouter = TestSendXcm; // Anyone can execute XCM messages locally... @@ -195,6 +215,11 @@ impl pallet_xcm::Config for Runtime { type Origin = Origin; 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>; } impl origin::Config for Runtime {} @@ -226,3 +251,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 0bc6cfc8ce7d..27f57f3ecf7e 100644 --- a/xcm/xcm-builder/tests/scenarios.rs +++ b/xcm/xcm-builder/tests/scenarios.rs @@ -17,8 +17,8 @@ mod mock; 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 +36,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] @@ -47,19 +47,16 @@ fn withdraw_and_deposit_works() { 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 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(); assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE - amount); @@ -78,13 +75,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 +106,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(); 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 response_info = QueryResponseInfo { + destination: Parachain(PARA_ID).into(), + query_id: 1234, + max_weight: 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 +143,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(); 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 +193,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(); // 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 +269,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 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 = 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 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/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index a6673aca7a92..3da4867515a8 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -37,7 +37,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(); @@ -119,7 +119,8 @@ fn query_response_fires() { let response = Response::ExecutionResult(None); let max_weight = 1_000_000; - let msg = Xcm(vec![QueryResponse { query_id, response, max_weight }]); + 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( @@ -154,7 +155,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() }), ) @@ -208,7 +209,8 @@ fn query_response_elicits_handler() { let response = Response::ExecutionResult(None); let max_weight = 1_000_000; - let msg = Xcm(vec![QueryResponse { query_id, response, max_weight }]); + let querier = Some(Here.into()); + let msg = Xcm(vec![QueryResponse { query_id, response, max_weight, querier }]); let execute = construct_extrinsic( &client, diff --git a/xcm/xcm-executor/src/assets.rs b/xcm/xcm-executor/src/assets.rs index 324e92dce9ff..0f09fde03806 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() } } @@ -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,6 +245,30 @@ impl Assets { .collect(); } + /// Returns `true` if `asset` is contained within `self`. + pub fn contains_asset(&self, asset: &MultiAsset) -> bool { + match asset { + MultiAsset { fun: Fungible(ref amount), ref id } => + self.fungible.get(id).map_or(false, |a| a >= amount), + MultiAsset { fun: NonFungible(ref instance), ref id } => + self.non_fungible.contains(&(id.clone(), instance.clone())), + } + } + + /// 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> { @@ -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,37 +433,62 @@ 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.clone(), 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.clone(), instance.clone())); + 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.clone(), 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())); + if maybe_limit.map_or(false, |l| masked.len() >= l) { + return masked + } } - }); - }, + }, MultiAssetFilter::Definite(assets) => for asset in assets.inner().iter() { match asset { @@ -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 e7d81dc8328c..1184f77c6712 100644 --- a/xcm/xcm-executor/src/config.rs +++ b/xcm/xcm-executor/src/config.rs @@ -15,14 +15,15 @@ // along with Polkadot. If not, see . use crate::traits::{ - ClaimAssets, ConvertOrigin, DropAssets, FilterAssetLocation, InvertLocation, OnResponse, - ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, + AssetExchange, AssetLock, ClaimAssets, ConvertOrigin, DropAssets, ExportXcm, FeeManager, + OnResponse, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, }; use frame_support::{ dispatch::{Dispatchable, Parameter}, + traits::{Contains, ContainsPair, Get, PalletsInfoAccess}, weights::{GetDispatchInfo, PostDispatchInfo}, }; -use xcm::latest::SendXcm; +use xcm::prelude::*; /// The trait to parameterize the `XcmExecutor`. pub trait Config { @@ -38,14 +39,14 @@ pub trait Config { /// How to get a call origin from a `OriginKind` value. type OriginConverter: ConvertOrigin<::Origin>; - /// 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; @@ -63,9 +64,34 @@ 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)>; } diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index a68f2db71b6c..9c6843b8b6a2 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -19,20 +19,20 @@ use frame_support::{ dispatch::{Dispatchable, Weight}, ensure, + traits::{Contains, ContainsPair, Get, PalletsInfoAccess}, weights::GetDispatchInfo, }; +use parity_scale_codec::{Decode, Encode}; +use sp_io::hashing::blake2_128; use sp_runtime::traits::Saturating; use sp_std::{marker::PhantomData, prelude::*}; -use xcm::latest::{ - Error as XcmError, ExecuteXcm, - Instruction::{self, *}, - MultiAssets, MultiLocation, Outcome, Response, SendXcm, Xcm, -}; +use xcm::latest::prelude::*; pub mod traits; use traits::{ - ClaimAssets, ConvertOrigin, DropAssets, FilterAssetLocation, InvertLocation, OnResponse, - ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, + validate_export, AssetExchange, AssetLock, ClaimAssets, ConvertOrigin, DropAssets, Enact, + ExportXcm, FeeManager, FeeReason, OnResponse, ShouldExecute, TransactAsset, + VersionChangeNotifier, WeightBounds, WeightTrader, }; mod assets; @@ -40,94 +40,181 @@ pub use assets::Assets; mod config; pub use config::Config; +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct FeesMode { + pub jit_withdraw: bool, +} + /// 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: u64, + total_refunded: u64, + error_handler: Xcm, + error_handler_weight: u64, + appendix: Xcm, + appendix_weight: u64, + transact_status: MaybeErrorCode, + fees_mode: FeesMode, + topic: Option<[u8; 32]>, _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) -> &u64 { + &self.total_surplus + } + pub fn set_total_surplus(&mut self, v: u64) { + self.total_surplus = v + } + pub fn total_refunded(&self) -> &u64 { + &self.total_refunded + } + pub fn set_total_refunded(&mut self, v: u64) { + 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) -> &u64 { + &self.error_handler_weight + } + pub fn set_error_handler_weight(&mut self, v: u64) { + 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) -> &u64 { + &self.appendix_weight + } + pub fn set_appendix_weight(&mut self, v: u64) { + 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.topic + } + pub fn set_topic(&mut self, v: Option<[u8; 32]>) { + self.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( + type Prepared = WeighedMessage; + fn prepare(mut message: Xcm) -> Result> { + match Config::Weigher::weight(&mut message) { + Ok(weight) => Ok(WeighedMessage(weight, message)), + Err(_) => Err(message), + } + } + fn execute( origin: impl Into, - mut message: Xcm, - weight_limit: Weight, + 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); @@ -139,7 +226,18 @@ impl ExecuteXcm for XcmExecutor { } } - vm.post_execute(xcm_weight) + vm.post_process(xcm_weight) + } + + fn charge_fees(origin: impl Into, 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(()) } } @@ -164,11 +262,12 @@ impl From 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.clone()), message_hash, topic: None }, original_origin: origin, trader: Config::Trader::new(), error: None, @@ -178,17 +277,23 @@ impl XcmExecutor { error_handler_weight: 0, appendix: Xcm(vec![]), appendix_weight: 0, + transact_status: Default::default(), + fees_mode: FeesMode { jit_withdraw: false }, + topic: None, _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, @@ -198,6 +303,7 @@ impl XcmExecutor { match &mut result { r @ Ok(()) => if let Err(e) = self.process_instruction(instr) { + log::trace!(target: "xcm::execute", "!!! ERROR: {:?}", e); *r = Err(ExecutorError { index: i as u32, xcm_error: e, weight: 0 }); }, Err(ref mut error) => @@ -211,15 +317,22 @@ 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 trap_weight = + Config::AssetTrap::drop_assets(&self.original_origin, self.holding, &self.context); weight_used.saturating_accrue(trap_weight); }; @@ -228,12 +341,35 @@ 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.clone() + } + + /// 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![]); @@ -257,102 +393,136 @@ impl XcmExecutor { r } + fn subsume_asset(&mut self, asset: MultiAsset) -> Result<(), XcmError> { + // worst-case, holding.len becomes 2 * holding_limit. + ensure!(self.holding.len() + 1 <= 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 { 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. fn process_instruction(&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)?.clone(); + 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)?.clone(); + 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::MultiLocationFull)?; 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)?.clone(); // 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)?.clone(); // 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) + let dispatch_origin = Config::OriginConverter::convert_origin(origin, origin_kind) .map_err(|_| XcmError::BadOrigin)?; let weight = message_call.get_dispatch_info().weight; ensure!(weight <= require_weight_at_most, XcmError::MaxWeightInvalid); - let actual_weight = match message_call.dispatch(dispatch_origin) { - Ok(post_info) => post_info.actual_weight, + let maybe_actual_weight = match message_call.dispatch(dispatch_origin) { + Ok(post_info) => { + self.transact_status = MaybeErrorCode::Success; + 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. + self.transact_status = error_and_info.error.encode().into(); error_and_info.post_info.actual_weight }, - } - .unwrap_or(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 @@ -365,47 +535,59 @@ impl XcmExecutor { 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), 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 @@ -417,29 +599,35 @@ 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); + 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 @@ -451,14 +639,11 @@ impl XcmExecutor { 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)?; @@ -480,41 +665,265 @@ 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(()) }, - ExchangeAsset { .. } => Err(XcmError::Unimplemented), + 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)?.clone(); + 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 } => { + 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, 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)?.clone(); + let (remote_asset, context) = Self::try_reanchor(asset.clone(), &unlocker)?; + let lock_ticket = + Config::AssetLocker::prepare_lock(unlocker.clone(), asset, origin.clone())?; + 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)?.clone(); + Config::AssetLocker::prepare_unlock(origin.clone(), asset, target)?.enact()?; + Ok(()) + }, + NoteUnlockable { asset, owner } => { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?.clone(); + Config::AssetLocker::note_unlockable(origin, asset, owner)?; + Ok(()) + }, + RequestUnlock { asset, locker } => { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?.clone(); + let remote_asset = Self::try_reanchor(asset.clone(), &locker)?.0; + let reduce_ticket = Config::AssetLocker::prepare_reduce_unlockable( + locker.clone(), + asset, + origin.clone(), + )?; + let msg = + Xcm::<()>(vec![UnlockAsset { asset: remote_asset, target: origin.clone() }]); + 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.topic = Some(topic); + Ok(()) + }, + ClearTopic => { + self.topic = None; + Ok(()) + }, + AliasOrigin(_) => Err(XcmError::NoPermission), 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.clone()) + .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..2d12606777c4 --- /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.clone(), 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..ee5c84f2c78b 100644 --- a/xcm/xcm-executor/src/traits/conversion.rs +++ b/xcm/xcm-executor/src/traits/conversion.rs @@ -16,7 +16,7 @@ use parity_scale_codec::{Decode, Encode}; use sp_std::{borrow::Borrow, prelude::*, result::Result}; -use xcm::latest::{MultiLocation, OriginKind}; +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. @@ -203,10 +203,3 @@ impl ConvertOrigin for Tuple { Err(origin) } } - -/// 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; -} diff --git a/xcm/xcm-executor/src/traits/drop_assets.rs b/xcm/xcm-executor/src/traits/drop_assets.rs index 5f82c5feb74b..45eefc9fba4e 100644 --- a/xcm/xcm-executor/src/traits/drop_assets.rs +++ b/xcm/xcm-executor/src/traits/drop_assets.rs @@ -17,15 +17,15 @@ use crate::Assets; use core::marker::PhantomData; use frame_support::{traits::Contains, weights::Weight}; -use xcm::latest::{MultiAssets, MultiLocation}; +use xcm::latest::{MultiAssets, MultiLocation, 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 { 0 } } @@ -35,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 { 0 } @@ -50,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 { 0 } @@ -63,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..4b4169ddc16d --- /dev/null +++ b/xcm/xcm-executor/src/traits/export.rs @@ -0,0 +1,117 @@ +// 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::*; + +pub trait ExportXcm { + 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( + network: NetworkId, + channel: u32, + 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, + 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, 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, + dest: InteriorMultiLocation, + msg: Xcm<()>, +) -> SendResult { + T::validate(network, channel, &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, + dest: InteriorMultiLocation, + msg: Xcm<()>, +) -> Result<(XcmHash, MultiAssets), SendError> { + let (ticket, price) = T::validate(network, channel, &mut Some(dest), &mut Some(msg.clone()))?; + 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..53bc6f964125 100644 --- a/xcm/xcm-executor/src/traits/mod.rs +++ b/xcm/xcm-executor/src/traits/mod.rs @@ -17,20 +17,39 @@ //! Various traits used in configuring the executor. mod conversion; -pub use conversion::{Convert, ConvertOrigin, Decoded, Encoded, Identity, InvertLocation, JustTry}; +pub use conversion::{Convert, ConvertOrigin, Decoded, Encoded, Identity, JustTry}; 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; -pub use should_execute::ShouldExecute; +pub use should_execute::{CheckSuspension, RejectReason, ShouldExecute}; 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, + }; +} diff --git a/xcm/xcm-executor/src/traits/on_response.rs b/xcm/xcm-executor/src/traits/on_response.rs index c5431e0e8cb9..2720dfd6ce5a 100644 --- a/xcm/xcm-executor/src/traits/on_response.rs +++ b/xcm/xcm-executor/src/traits/on_response.rs @@ -15,29 +15,45 @@ // along with Polkadot. If not, see . use frame_support::weights::Weight; -use xcm::latest::{Error as XcmError, MultiLocation, QueryId, Response, Result as XcmResult}; +use xcm::latest::{ + Error as XcmError, MultiLocation, QueryId, Response, Result as XcmResult, 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 { 0 } @@ -48,26 +64,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: u64, + 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, _: u64, _: &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 5f94db0066b4..6acc9036a381 100644 --- a/xcm/xcm-executor/src/traits/should_execute.rs +++ b/xcm/xcm-executor/src/traits/should_execute.rs @@ -15,8 +15,31 @@ // along with Polkadot. If not, see . use frame_support::weights::Weight; +use parity_scale_codec::{Decode, Encode}; use sp_std::result::Result; -use xcm::latest::{MultiLocation, Xcm}; +use xcm::latest::{Instruction, MultiLocation}; + +/// The reason why a given XCM is not permitted to execute. +#[derive(Clone, Debug, PartialEq, Eq, Decode, Encode)] +pub enum RejectReason { + /// The supplied weight credit is not enough to pay for the weight required. + InsufficientCredit, + /// None of the barrier rules in a tuple matched successfully. + NoRulesMatched, + /// The series of `DescendOrigin` instructions would overflow the origin MultiLocation. + OriginMultiLocationTooLong, + /// Does not pass the barrier yet, but might pass in a later block. + Suspended, + /// Barrier does not recognize the incoming XCM format. + UnexpectedMessageFormat, + /// The receiving chain did not expect a response XCM from the origin. + UnexpectedResponse, + /// The origin is not trusted to pass this barrier. + UntrustedOrigin, + /// The incoming XCM contains a weight limit that is lower than the required weight, as weighed + /// by the receiver. + WeightLimitTooLow, +} /// Trait to determine whether the execution engine should actually execute a given XCM. /// @@ -26,41 +49,80 @@ 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<(), ()>; + ) -> Result<(), RejectReason>; } #[impl_trait_for_tuples::impl_for_tuples(30)] impl ShouldExecute for Tuple { fn should_execute( origin: &MultiLocation, - message: &mut Xcm, + instructions: &mut [Instruction], max_weight: Weight, weight_credit: &mut Weight, - ) -> Result<(), ()> { + ) -> Result<(), RejectReason> { 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(()), - _ => (), - } + Err(e) => log::trace!( + target: "xcm::should_execute", + "barrier rule rejected message with reason: {:?}", e + ), + }; )* ); log::trace!( target: "xcm::should_execute", - "did not pass barrier: origin: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}", + "did not pass any barrier rules: origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}", origin, - message, + instructions, max_weight, weight_credit, ); - Err(()) + Err(RejectReason::NoRulesMatched) + } +} + +/// Trait to determine whether the execution engine is suspended from executing a given XCM. +/// +/// The trait method is given the same parameters as `ShouldExecute::should_execute`, so that the +/// implementer will have all the context necessary to determine whether or not to suspend the +/// XCM executor. +/// +/// Can be chained together in tuples to have multiple rounds of checks. If all of the tuple +/// elements returns false, then execution is not suspended. Otherwise, execution is suspended +/// if any of the tuple elements returns true. +pub trait CheckSuspension { + fn is_suspended( + origin: &MultiLocation, + instructions: &mut [Instruction], + max_weight: Weight, + weight_credit: &mut Weight, + ) -> bool; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl CheckSuspension for Tuple { + fn is_suspended( + origin: &MultiLocation, + instruction: &mut [Instruction], + max_weight: Weight, + weight_credit: &mut Weight, + ) -> bool { + for_tuples!( #( + if Tuple::is_suspended(origin, instruction, max_weight, weight_credit) { + return true + } + )* ); + + false } } 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..13cc9b6a4a41 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. /// @@ -29,7 +29,11 @@ pub trait TransactAsset { /// Ensure that `check_in` will result in `Ok`. /// /// 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,7 @@ 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) {} /// An asset has been teleported out to the given destination. This should do whatever housekeeping is needed. /// @@ -58,20 +62,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 +100,7 @@ pub trait TransactAsset { _asset: &MultiAsset, _from: &MultiLocation, _to: &MultiLocation, + _context: &XcmContext, ) -> Result { Err(XcmError::Unimplemented) } @@ -100,12 +113,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 +129,69 @@ 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 check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) { for_tuples!( #( - Tuple::check_out(dest, what); + 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 +200,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 +230,27 @@ 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 deposit_asset( + _what: &MultiAsset, + _who: &MultiLocation, + _context: &XcmContext, + ) -> XcmResult { Err(XcmError::AssetNotFound) } - fn withdraw_asset(_what: &MultiAsset, _who: &MultiLocation) -> Result { + fn withdraw_asset( + _what: &MultiAsset, + _who: &MultiLocation, + _context: Option<&XcmContext>, + ) -> Result { Err(XcmError::AssetNotFound) } @@ -223,6 +258,7 @@ mod tests { _what: &MultiAsset, _from: &MultiLocation, _to: &MultiLocation, + _context: &XcmContext, ) -> Result { Err(XcmError::AssetNotFound) } @@ -230,15 +266,27 @@ 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 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 +294,7 @@ mod tests { _what: &MultiAsset, _from: &MultiLocation, _to: &MultiLocation, + _context: &XcmContext, ) -> Result { Err(XcmError::Overflow) } @@ -253,15 +302,27 @@ 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 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 +330,7 @@ mod tests { _what: &MultiAsset, _from: &MultiLocation, _to: &MultiLocation, + _context: &XcmContext, ) -> Result { Ok(Assets::default()) } @@ -280,7 +342,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 +355,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 +370,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 +383,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 8a36fdfdcf78..2cb352c397a3 100644 --- a/xcm/xcm-simulator/example/Cargo.toml +++ b/xcm/xcm-simulator/example/Cargo.toml @@ -8,14 +8,17 @@ edition = "2021" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } scale-info = { version = "2.1.1", 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,18 @@ 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", + "xcm/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 9cb9617c76ea..d9c08a5f369f 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() +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::{Origin, Runtime, 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(Origin::root(), 1, ALICE, true), Ok(())); + assert_eq!(Uniques::mint(Origin::signed(ALICE), 1, 42, child_account_id(1)), Ok(())); + }); ext } @@ -106,7 +139,7 @@ mod tests { use codec::Encode; use frame_support::assert_ok; - use xcm::latest::prelude::*; + 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(); @@ -127,7 +167,7 @@ mod tests { Here, Parachain(1), Xcm(vec![Transact { - origin_type: OriginKind::SovereignAccount, + origin_kind: OriginKind::SovereignAccount, require_weight_at_most: INITIAL_BALANCE as u64, call: remark.encode().into(), }]), @@ -154,7 +194,7 @@ mod tests { Here, Parent, Xcm(vec![Transact { - origin_type: OriginKind::SovereignAccount, + origin_kind: OriginKind::SovereignAccount, require_weight_at_most: INITIAL_BALANCE as u64, call: remark.encode().into(), }]), @@ -182,7 +222,7 @@ mod tests { Here, (Parent, Parachain(2)), Xcm(vec![Transact { - origin_type: OriginKind::SovereignAccount, + origin_kind: OriginKind::SovereignAccount, require_weight_at_most: INITIAL_BALANCE as u64, call: remark.encode().into(), }]), @@ -206,13 +246,13 @@ mod tests { Relay::execute_with(|| { assert_ok!(RelayChainPalletXcm::reserve_transfer_assets( relay_chain::Origin::signed(ALICE), - Box::new(X1(Parachain(1)).into().into()), - Box::new(X1(AccountId32 { network: Any, id: ALICE.into() }).into().into()), + Box::new(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 ); }); @@ -226,6 +266,269 @@ 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::Origin::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::Origin::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::Origin::root(), + 2, + ALICE, + false + )); + assert_ok!(relay_chain::Uniques::mint( + relay_chain::Origin::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::Origin::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::Origin::root(), + 2, + ALICE, + false + )); + assert_ok!(relay_chain::Uniques::mint( + relay_chain::Origin::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: 1_000_000_000, + call: parachain::Call::from(pallet_uniques::Call::::create { + class: (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::class_owner((Parent, 2u64).into()), + Some(parent_account_id()) + ); + }); + } + /// Scenario: /// A parachain transfers funds on the relay chain to another parachain account. /// @@ -240,11 +543,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())); @@ -252,10 +551,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 + ); }); } @@ -276,16 +578,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: 1_000_000_000, + }, 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 @@ -296,11 +596,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,6 +614,7 @@ mod tests { query_id: query_id_set, response: Response::Assets(MultiAssets::new()), max_weight: 1_000_000_000, + querier: Some(Here.into()), }])], ); }); diff --git a/xcm/xcm-simulator/example/src/parachain.rs b/xcm/xcm-simulator/example/src/parachain.rs index 614d50e4fc6e..77dfa962bf37 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_PER_SECOND, Weight}, }; use sp_core::H256; @@ -37,12 +37,21 @@ 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, 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 +105,58 @@ 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 class(i: u16) -> MultiLocation { + GeneralIndex(i as u128).into() + } + fn instance(i: u16) -> AssetInstance { + AssetInstance::Index(i as u128) + } +} + +impl pallet_uniques::Config for Runtime { + type Event = Event; + type ClassId = MultiLocation; + type InstanceId = AssetInstance; + type Currency = Balances; + type CreateOrigin = ForeignCreators; + type ForceOrigin = frame_system::EnsureRoot; + type ClassDeposit = frame_support::traits::ConstU128<1_000>; + type InstanceDeposit = 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 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: Origin, 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) -> Origin { + pallet_xcm::Origin::Xcm(a.clone()).into() + } +} + parameter_types! { pub const ReservedXcmpWeight: Weight = WEIGHT_PER_SECOND / 4; pub const ReservedDmpWeight: Weight = WEIGHT_PER_SECOND / 4; @@ -104,13 +165,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 = ( @@ -123,30 +185,57 @@ parameter_types! { pub const UnitWeightCost: Weight = 1; pub KsmPerSecond: (AssetId, u128) = (Concrete(Parent.into()), 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, + Nothing, + (), + >, +); 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 Call = Call; 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 ResponseHandler = (); type AssetTrap = (); + type AssetLocker = (); + type AssetExchanger = (); type AssetClaims = (); type SubscriptionService = (); + type PalletInstancesInfo = (); + type FeeManager = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; } #[frame_support::pallet] @@ -219,10 +308,11 @@ 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) { + 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(w), Event::Success(Some(hash))), // As far as the caller is concerned, this was dispatched without error, so @@ -267,19 +357,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); - >::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)); + }, }, } } @@ -305,11 +394,16 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Nothing; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; 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 UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -325,5 +419,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 a648ba96ba70..b7a7a13f0bf6 100644 --- a/xcm/xcm-simulator/example/src/relay_chain.rs +++ b/xcm/xcm-simulator/example/src/relay_chain.rs @@ -18,7 +18,7 @@ use frame_support::{ construct_runtime, parameter_types, - traits::{Everything, Nothing}, + traits::{AsEnsureOriginWithArg, Everything, Nothing}, weights::Weight, }; use sp_core::H256; @@ -28,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, NonFungiblesAdapter, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, }; -use xcm_executor::{Config, XcmExecutor}; +use xcm_executor::{traits::JustTry, Config, XcmExecutor}; pub type AccountId = AccountId32; pub type Balance = u128; @@ -87,6 +88,26 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; } +impl pallet_uniques::Config for Runtime { + type Event = Event; + type ClassId = u32; + type InstanceId = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type ClassDeposit = frame_support::traits::ConstU128<1_000>; + type InstanceDeposit = 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 WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); +} + impl shared::Config for Runtime {} impl configuration::Config for Runtime { @@ -94,30 +115,43 @@ 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: Weight = 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, + Nothing, + (), + >, +); type LocalOriginConverter = ( - SovereignSignedViaLocation, + SovereignSignedViaLocation, ChildParachainAsNative, - SignedAccountId32AsNative, + SignedAccountId32AsNative, ChildSystemParachainAsSuperuser, ); parameter_types! { pub const BaseXcmWeight: Weight = 1_000; - pub KsmPerSecond: (AssetId, u128) = (Concrete(KsmLocation::get()), 1); + pub TokensPerSecond: (AssetId, u128) = (Concrete(TokenLocation::get()), 1_000_000_000_000); pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } pub type XcmRouter = super::RelayChainXcmRouter; @@ -131,17 +165,24 @@ 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; } -pub type LocalOriginToLocation = SignedToAccountId32; +pub type LocalOriginToLocation = SignedToAccountId32; impl pallet_xcm::Config for Runtime { type Event = Event; @@ -154,11 +195,16 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; 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>; } parameter_types! { @@ -189,5 +235,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/src/parachain.rs b/xcm/xcm-simulator/fuzzer/src/parachain.rs index 3326875206b2..11eacc7f5963 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 = ( @@ -123,6 +123,7 @@ parameter_types! { pub const UnitWeightCost: Weight = 1; pub KsmPerSecond: (AssetId, u128) = (Concrete(Parent.into()), 1); pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } pub type LocalAssetTransactor = @@ -139,14 +140,21 @@ 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 ResponseHandler = (); type AssetTrap = (); + type AssetLocker = (); + type AssetExchanger = (); type AssetClaims = (); type SubscriptionService = (); + type PalletInstancesInfo = (); + type FeeManager = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; } #[frame_support::pallet] @@ -219,10 +227,11 @@ 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) { + 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(w), Event::Success(Some(hash))), // As far as the caller is concerned, this was dispatched without error, so @@ -277,7 +286,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); + let outcome = T::XcmExecutor::execute_xcm(Parent, x.clone(), id, limit); >::append(x); Self::deposit_event(Event::ExecutedDownward(id, outcome)); }, @@ -305,11 +314,16 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Nothing; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; 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 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 a648ba96ba70..4a0267214a8a 100644 --- a/xcm/xcm-simulator/fuzzer/src/relay_chain.rs +++ b/xcm/xcm-simulator/fuzzer/src/relay_chain.rs @@ -31,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}; @@ -94,30 +94,31 @@ 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: Weight = 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 const UnitWeightCost: Weight = 1_000; } 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: Weight = 1_000; - pub KsmPerSecond: (AssetId, u128) = (Concrete(KsmLocation::get()), 1); + pub KsmPerSecond: (AssetId, u128) = (Concrete(TokenLocation::get()), 1); pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } pub type XcmRouter = super::RelayChainXcmRouter; @@ -131,17 +132,24 @@ 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 ResponseHandler = (); type AssetTrap = (); + type AssetLocker = (); + type AssetExchanger = (); type AssetClaims = (); type SubscriptionService = (); + type PalletInstancesInfo = (); + type FeeManager = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; } -pub type LocalOriginToLocation = SignedToAccountId32; +pub type LocalOriginToLocation = SignedToAccountId32; impl pallet_xcm::Config for Runtime { type Event = Event; @@ -154,11 +162,16 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; 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>; } parameter_types! { diff --git a/xcm/xcm-simulator/src/lib.rs b/xcm/xcm-simulator/src/lib.rs index 5e563e153dba..6a60eecb71d4 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; @@ -76,6 +76,10 @@ pub fn encode_xcm(message: Xcm<()>, message_kind: MessageKind) -> Vec { } } +pub fn fake_message_hash(message: &Xcm) -> XcmHash { + message.using_encoded(blake2_256) +} + #[macro_export] #[rustfmt::skip] macro_rules! decl_test_relay_chain { @@ -210,6 +214,7 @@ macro_rules! decl_test_network { parachains = vec![ $( ($para_id:expr, $parachain:ty), )* ], } ) => { + use $crate::Encode; pub struct $name; impl $name { @@ -296,45 +301,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) } } };