From 470683fb48ec9be0ebbb701feee68952071652ae Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Fri, 31 Mar 2023 15:32:32 -0300 Subject: [PATCH 01/73] Initialize mdbook --- .gitignore | 1 + book.toml | 6 ++++++ src/SUMMARY.md | 3 +++ src/overview.md | 1 + src/xcm.md | 4 ++++ 5 files changed, 15 insertions(+) create mode 100644 .gitignore create mode 100644 book.toml create mode 100644 src/SUMMARY.md create mode 100644 src/overview.md create mode 100644 src/xcm.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7585238 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +book diff --git a/book.toml b/book.toml new file mode 100644 index 0000000..600812e --- /dev/null +++ b/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["Francisco Aguirre"] +language = "en" +multilingual = false +src = "src" +title = "XCM Documentation" diff --git a/src/SUMMARY.md b/src/SUMMARY.md new file mode 100644 index 0000000..3961b19 --- /dev/null +++ b/src/SUMMARY.md @@ -0,0 +1,3 @@ +# Summary + +[XCM](xcm.md) diff --git a/src/overview.md b/src/overview.md new file mode 100644 index 0000000..07dd0c5 --- /dev/null +++ b/src/overview.md @@ -0,0 +1 @@ +# Overview diff --git a/src/xcm.md b/src/xcm.md new file mode 100644 index 0000000..55ee118 --- /dev/null +++ b/src/xcm.md @@ -0,0 +1,4 @@ +# XCM + +This is the place where we will brainstorm XCM documentation and tutorial ideas. +It will eventually be in a public centralized place for everyone to find and use. From 9c509596b73d4703766f875110ba19801b934fef Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Fri, 31 Mar 2023 18:31:04 -0300 Subject: [PATCH 02/73] Brainstorm possible documentation structure --- src/SUMMARY.md | 37 ++++++++++++++++++++++++++++++++++++- src/xcm.md | 2 +- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 3961b19..726c633 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -1,3 +1,38 @@ # Summary -[XCM](xcm.md) +[XCM: Cross-Consensus Messaging](xcm.md) + +- [Overview]() + - [The Promise of Interoperability]() + - [A Format, not a Protocol]() + - [The Tools]() + - [The Ecosystem]() +- [Quickstart]() + - [XCM Simulator]() + - [First Look at a message]() +- [Fundamentals]() + - [MultiLocation]() + - [MultiAsset]() + - [Fees]() +- [Tutorial]() + - [Transfers]() + - [Locks]() + - [Origin]() + - [Fees]() + - [Channels]() + - [Expectations]() + - [Queries]() + - [Transact]() + - [Misc]() +- [Transport Protocols]() + - [VMP]() + - [HRMP]() + - [XCMP]() +- [A few notes on testing]() + +# Annex + +- [Full Examples]() +- [Instructions Reference]() + +[Next Steps]() diff --git a/src/xcm.md b/src/xcm.md index 55ee118..819a650 100644 --- a/src/xcm.md +++ b/src/xcm.md @@ -1,4 +1,4 @@ -# XCM +# XCM: Cross-Consensus Messaging This is the place where we will brainstorm XCM documentation and tutorial ideas. It will eventually be in a public centralized place for everyone to find and use. From 767b7d3287a75f8ae7c9f7486455bf7a43df140a Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 4 Apr 2023 11:18:39 -0300 Subject: [PATCH 03/73] Restructure --- src/SUMMARY.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 726c633..e713a5e 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -5,34 +5,38 @@ - [Overview]() - [The Promise of Interoperability]() - [A Format, not a Protocol]() - - [The Tools]() - - [The Ecosystem]() + - [Architecture]() - [Quickstart]() - [XCM Simulator]() - - [First Look at a message]() + - [First Look at an XCM]() - [Fundamentals]() - [MultiLocation]() - [MultiAsset]() + - [Responses]() - [Fees]() -- [Tutorial]() +- [A Journey through XCM]() - [Transfers]() - - [Locks]() - - [Origin]() - [Fees]() - - [Channels]() - [Expectations]() - [Queries]() - - [Transact]() + - [Locks]() + - [Origin]() + - [Channels]() + - [When all else fails]() - [Misc]() - [Transport Protocols]() - [VMP]() - [HRMP]() - [XCMP]() -- [A few notes on testing]() +- [Testing]() + - [Separation of concerns]() + - [Simulating message execution]() + - [What if I configured it differently?]() + - [The nuclear solution]() -# Annex +# Reference -- [Full Examples]() -- [Instructions Reference]() +- [Cookbook]() +- [All Instructions]() [Next Steps]() From e5cbefe9360ef4e2eb03a297da50e818e2041efd Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Fri, 7 Apr 2023 10:44:36 -0300 Subject: [PATCH 04/73] Swap some chapters around --- src/SUMMARY.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index e713a5e..a5fc430 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -24,15 +24,15 @@ - [Channels]() - [When all else fails]() - [Misc]() +- [Testing]() + - [Separation of concerns]() + - [Simulating message execution]() + - [How to test my own configuration]() + - [Testing the full XCM Journey]() - [Transport Protocols]() - [VMP]() - [HRMP]() - [XCMP]() -- [Testing]() - - [Separation of concerns]() - - [Simulating message execution]() - - [What if I configured it differently?]() - - [The nuclear solution]() # Reference From 2ed4b0113a3fd405f8502c9c471651fc1cd1449c Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Thu, 13 Apr 2023 14:21:00 +0200 Subject: [PATCH 05/73] quickstart draft --- src/SUMMARY.md | 6 +++--- src/quickstart/README.md | 31 +++++++++++++++++++++++++++++ src/quickstart/first-look.md | 36 ++++++++++++++++++++++++++++++++++ src/quickstart/xcm-emulator.md | 9 +++++++++ 4 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 src/quickstart/README.md create mode 100644 src/quickstart/first-look.md create mode 100644 src/quickstart/xcm-emulator.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index a5fc430..e82cfa6 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -6,9 +6,9 @@ - [The Promise of Interoperability]() - [A Format, not a Protocol]() - [Architecture]() -- [Quickstart]() - - [XCM Simulator]() - - [First Look at an XCM]() +- [Quickstart](quickstart/README.md) + - [XCM Emulator](quickstart/xcm-emulator.md) + - [First Look at an XCM](quickstart/first-look.md) - [Fundamentals]() - [MultiLocation]() - [MultiAsset]() diff --git a/src/quickstart/README.md b/src/quickstart/README.md new file mode 100644 index 0000000..3f3d2a9 --- /dev/null +++ b/src/quickstart/README.md @@ -0,0 +1,31 @@ +# Quickstart + +The XCM code can be found in [polkadot repository](https://github.com/paritytech/polkadot/tree/master/xcm). + +### Rust & Cargo +A pre-requisite for using XCM is to have a stable Rust version and Cargo installed. Here's an [installation guide](https://docs.substrate.io/install/) on how to install rust and cargo. + +### Running the Examples + +All examples in the documentation are located in the [examples repository](). Follow the following steps to run the `first-look` example. First clone the repository: + +```shell +git clone git@github.com:vstam1/xcm-examples.git +cd xcm-examples +``` + +To run the first-look example, run the following line: + +```shell +cargo test -p xcm-examples parachain_a_simple_transfer -- --nocapture +``` + +It should show you the following output: + +```shell +running 1 test +test first_look::tests::parachain_a_simple_transfer ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.01s +``` + diff --git a/src/quickstart/first-look.md b/src/quickstart/first-look.md new file mode 100644 index 0000000..be5ee9f --- /dev/null +++ b/src/quickstart/first-look.md @@ -0,0 +1,36 @@ +## First Look +In this section, we take you through a simple example of an XCM. In this example, we withdraw the native token from the account of Alice and deposit this token in the account of Bob. This message simulates a transfer between two accounts in the same consensus system (`SimpleParachain`). Find here the [code example](). + +### Message +The message consists of three instructions: WithdrawAsset, BuyExecution, and DepositAsset. + +##### WithdrawAsset +```rust +WithdrawAsset((Here, amount).into()) +``` + +The first instruction takes as an input the [MultiAsset]() that should be withdrawn. The MultiAsset describes the native parachain token with the `Here` keyword. The `amount` parameter is the number of tokens that are transferred. The withdrawal account depends on the Origin of the message. In this example the Origin of the message is Alice. +The WithdrawAsset instruction moves `amount` number of native tokens from Alice's account into the `Holding register`. + +##### BuyExecution +```rust +BuyExecution{fees: (Here, amount).into(), weight_limit: WeightLimit::Unlimited} +``` +To execute XCM instructions, execution time (weight) has to be bought. The amount of execution time depends on the number and type of instructions in the XCM. The `BuyExecution` instruction pays for the weight using the `fees`. The `fees` parameter describes the asset in the `Holding register` that should be used for paying for the weight. The `weight_limit` defines the maximum amount of fees that can be used for buying execution time. See [fees]() for more information about the fee payments. + +##### DepositAsset +```rust +DepositAsset { + assets: All.into(), + beneficiary: MultiLocation { + parents: 0, + interior: X1(Junction::AccountId32 { + network: Some(NetworkId::Kusama), + id: BOB.clone().into() + }), + }.into() +} +``` +The DepositAsset instruction is used to deposit funds from the holding register into the account of the `beneficiary`. We don’t actually know how much is remaining in the Holding Register after the BuyExecution instruction, but that doesn’t matter since we specify a wildcard for the asset(s) which should be deposited. In this case, the wildcard is `All`, meaning that all assets in the Holding register should be deposited. The `beneficiary` in this case is the account of Bob in the current consensus system. + +When the three instructions are combined, we withdraw `amount` native tokens from the account of Alice, pay for the execution of the instructions, and deposit the remaining tokens in the account of Bob. \ No newline at end of file diff --git a/src/quickstart/xcm-emulator.md b/src/quickstart/xcm-emulator.md new file mode 100644 index 0000000..482689e --- /dev/null +++ b/src/quickstart/xcm-emulator.md @@ -0,0 +1,9 @@ +### xcm-emulator +Setting up a live network with multiple connected parachains for testing XCM is not straight forward. The XCM-emulator was created as a solution to this problem. The XCM-emulator is a network emulator specifically designed for testing and playing around with XCM. It emulates the sending, delivery and execution of XCM instructions, with the assumption that the message can always be delivered to and executed in the destination. + +The xcm-emulator can use production relay chain and parachain runtimes. Users can plug in Kusama, Statemine, or their custom runtime etc. With up-to-date chain specs, it's able to verify if specific XCM messages work in live networks. + +The emulator makes it possible to quickly set up a custom emulated network. [This example](TODO) shows how to setup a network of one relaychain and three parachains. + + + From daa24ff5c372ef76abbbd7c50593d57bbb1d1878 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Fri, 14 Apr 2023 11:55:09 +0200 Subject: [PATCH 06/73] setup fundementals --- src/SUMMARY.md | 6 +++--- src/fundamentals/README.md | 0 src/fundamentals/multiasset.md | 1 + src/fundamentals/multilocation.md | 6 ++++++ 4 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 src/fundamentals/README.md create mode 100644 src/fundamentals/multiasset.md create mode 100644 src/fundamentals/multilocation.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index a5fc430..0bee46b 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -9,9 +9,9 @@ - [Quickstart]() - [XCM Simulator]() - [First Look at an XCM]() -- [Fundamentals]() - - [MultiLocation]() - - [MultiAsset]() +- [Fundamentals](fundamentals/README.md) + - [MultiLocation](fundamentals/multilocation.md) + - [MultiAsset](fundamentals/multiasset.md) - [Responses]() - [Fees]() - [A Journey through XCM]() diff --git a/src/fundamentals/README.md b/src/fundamentals/README.md new file mode 100644 index 0000000..e69de29 diff --git a/src/fundamentals/multiasset.md b/src/fundamentals/multiasset.md new file mode 100644 index 0000000..bad7d1f --- /dev/null +++ b/src/fundamentals/multiasset.md @@ -0,0 +1 @@ +### MultiAsset diff --git a/src/fundamentals/multilocation.md b/src/fundamentals/multilocation.md new file mode 100644 index 0000000..6d788c1 --- /dev/null +++ b/src/fundamentals/multilocation.md @@ -0,0 +1,6 @@ +### MultiLocation + + + + +- functions on multilocation https://paritytech.github.io/polkadot/doc/xcm/v3/struct.MultiLocation.html \ No newline at end of file From 486d4b060bce03fba4e1f3be591e961cdc99be07 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Mon, 17 Apr 2023 14:06:09 +0200 Subject: [PATCH 07/73] edit quickstart --- src/SUMMARY.md | 2 +- src/quickstart/README.md | 4 ++-- src/quickstart/first-look.md | 31 +++++++++++++++++++++++++++---- src/quickstart/xcm-emulator.md | 9 --------- src/quickstart/xcm-simulator.md | 13 +++++++++++++ 5 files changed, 43 insertions(+), 16 deletions(-) delete mode 100644 src/quickstart/xcm-emulator.md create mode 100644 src/quickstart/xcm-simulator.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index e82cfa6..cd9718d 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -7,7 +7,7 @@ - [A Format, not a Protocol]() - [Architecture]() - [Quickstart](quickstart/README.md) - - [XCM Emulator](quickstart/xcm-emulator.md) + - [XCM Simulator](quickstart/xcm-simulator.md) - [First Look at an XCM](quickstart/first-look.md) - [Fundamentals]() - [MultiLocation]() diff --git a/src/quickstart/README.md b/src/quickstart/README.md index 3f3d2a9..0bad600 100644 --- a/src/quickstart/README.md +++ b/src/quickstart/README.md @@ -17,14 +17,14 @@ cd xcm-examples To run the first-look example, run the following line: ```shell -cargo test -p xcm-examples parachain_a_simple_transfer -- --nocapture +cargo test -p xcm-examples para_a_simple_transfer -- --nocapture ``` It should show you the following output: ```shell running 1 test -test first_look::tests::parachain_a_simple_transfer ... ok +test first_look::tests::para_a_simple_transfer ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.01s ``` diff --git a/src/quickstart/first-look.md b/src/quickstart/first-look.md index be5ee9f..21435c0 100644 --- a/src/quickstart/first-look.md +++ b/src/quickstart/first-look.md @@ -1,8 +1,24 @@ ## First Look -In this section, we take you through a simple example of an XCM. In this example, we withdraw the native token from the account of Alice and deposit this token in the account of Bob. This message simulates a transfer between two accounts in the same consensus system (`SimpleParachain`). Find here the [code example](). +In this section, we take you through a simple example of an XCM. In this example, we withdraw the native token from the account of Alice and deposit this token in the account of Bob. This message simulates a transfer between two accounts in the same consensus system (`ParaA`). Find here the [code example](). ### Message -The message consists of three instructions: WithdrawAsset, BuyExecution, and DepositAsset. +```rust + let message = Xcm(vec![ + WithdrawAsset((Here, amount).into()), + BuyExecution{fees: (Here, amount).into(), weight_limit: WeightLimit::Unlimited}, + DepositAsset { + assets: All.into(), + beneficiary: MultiLocation { + parents: 0, + interior: X1(Junction::AccountId32 { + network: Some(NetworkId::Kusama), + id: BOB.clone().into() + }), + }.into() + } +]); +``` +The message consists of three instructions: WithdrawAsset, BuyExecution, and DepositAsset. In the following sections we will go over each of these instructions. ##### WithdrawAsset ```rust @@ -16,7 +32,7 @@ The WithdrawAsset instruction moves `amount` number of native tokens from Alice' ```rust BuyExecution{fees: (Here, amount).into(), weight_limit: WeightLimit::Unlimited} ``` -To execute XCM instructions, execution time (weight) has to be bought. The amount of execution time depends on the number and type of instructions in the XCM. The `BuyExecution` instruction pays for the weight using the `fees`. The `fees` parameter describes the asset in the `Holding register` that should be used for paying for the weight. The `weight_limit` defines the maximum amount of fees that can be used for buying execution time. See [fees]() for more information about the fee payments. +To execute XCM instructions, weight has to be bought. The amount of weight depends on the number and type of instructions in the XCM. The `BuyExecution` instruction pays for the weight using the `fees`. The `fees` parameter describes the asset in the `Holding register` that should be used for paying for the weight. The `weight_limit` defines the maximum amount of fees that can be used for buying weight. See [fees]() for more information about the fees in XCM. ##### DepositAsset ```rust @@ -33,4 +49,11 @@ DepositAsset { ``` The DepositAsset instruction is used to deposit funds from the holding register into the account of the `beneficiary`. We don’t actually know how much is remaining in the Holding Register after the BuyExecution instruction, but that doesn’t matter since we specify a wildcard for the asset(s) which should be deposited. In this case, the wildcard is `All`, meaning that all assets in the Holding register should be deposited. The `beneficiary` in this case is the account of Bob in the current consensus system. -When the three instructions are combined, we withdraw `amount` native tokens from the account of Alice, pay for the execution of the instructions, and deposit the remaining tokens in the account of Bob. \ No newline at end of file +When the three instructions are combined, we withdraw `amount` native tokens from the account of Alice, pay for the execution of the instructions, and deposit the remaining tokens in the account of Bob. + + +##### What next? +Now that we have taken a first look at an XCM, we can dive deeper into all the XCM instructions. +For an overview of the instructions check out the [xcm-format](https://github.com/paritytech/xcm-format#5-the-xcvm-instruction-set). +Or check out examples for each of the instruction in [A Journey through XCM](). +To get a better understanding about MultiLocations, MultiAssets, and other fundamental concepts in XCM, check out the [fundamentals chapter](fundamentals/README.md). diff --git a/src/quickstart/xcm-emulator.md b/src/quickstart/xcm-emulator.md deleted file mode 100644 index 482689e..0000000 --- a/src/quickstart/xcm-emulator.md +++ /dev/null @@ -1,9 +0,0 @@ -### xcm-emulator -Setting up a live network with multiple connected parachains for testing XCM is not straight forward. The XCM-emulator was created as a solution to this problem. The XCM-emulator is a network emulator specifically designed for testing and playing around with XCM. It emulates the sending, delivery and execution of XCM instructions, with the assumption that the message can always be delivered to and executed in the destination. - -The xcm-emulator can use production relay chain and parachain runtimes. Users can plug in Kusama, Statemine, or their custom runtime etc. With up-to-date chain specs, it's able to verify if specific XCM messages work in live networks. - -The emulator makes it possible to quickly set up a custom emulated network. [This example](TODO) shows how to setup a network of one relaychain and three parachains. - - - diff --git a/src/quickstart/xcm-simulator.md b/src/quickstart/xcm-simulator.md new file mode 100644 index 0000000..1620384 --- /dev/null +++ b/src/quickstart/xcm-simulator.md @@ -0,0 +1,13 @@ +### xcm-simulator +Setting up a live network with multiple connected parachains for testing XCM is not straight forward. The `XCM-simulator` was created as a solution to this problem. The XCM-simulator is a network simulator specifically designed for testing and playing around with XCM. It uses mock relay chain and parachain runtime. + +For testing xcm configurations for live runtime environments we use the `XCM-emulator`. The XCM-emulator can use production relay chain and parachain runtimes. Users can plug in Kusama, Statemine, or their custom runtime etc. With up-to-date chain specs, it's able to verify if specific XCM messages work in live networks. The specific use cases will be further explained in the chapter on [testing](testing/README.md). + +In the next section we will take a first look at an XCM. The XCM-simulator is used for the example code. + +[Next: First Look at an XCM](first-look.md) + + + + + From 5d6133d2410dd11b382af024be035fd78796a352 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Wed, 19 Apr 2023 16:59:00 +0200 Subject: [PATCH 08/73] add multilocation and multiasset drafts --- src/SUMMARY.md | 8 +- src/fundamentals/README.md | 6 + src/fundamentals/fees.md | 1 + .../images/MultiLocation_Example.png | Bin 0 -> 45421 bytes .../images/MultiLocation_simple_example.png | Bin 0 -> 16373 bytes src/fundamentals/multiasset.md | 125 +++++++++++++ src/fundamentals/multilocation.md | 6 - src/fundamentals/multilocation/README.md | 64 +++++++ src/fundamentals/multilocation/example.md | 8 + src/fundamentals/multilocation/junction.md | 172 ++++++++++++++++++ src/fundamentals/responses.md | 1 + 11 files changed, 382 insertions(+), 9 deletions(-) create mode 100644 src/fundamentals/fees.md create mode 100644 src/fundamentals/images/MultiLocation_Example.png create mode 100644 src/fundamentals/images/MultiLocation_simple_example.png delete mode 100644 src/fundamentals/multilocation.md create mode 100644 src/fundamentals/multilocation/README.md create mode 100644 src/fundamentals/multilocation/example.md create mode 100644 src/fundamentals/multilocation/junction.md create mode 100644 src/fundamentals/responses.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 0bee46b..ba9257e 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -10,10 +10,12 @@ - [XCM Simulator]() - [First Look at an XCM]() - [Fundamentals](fundamentals/README.md) - - [MultiLocation](fundamentals/multilocation.md) + - [MultiLocation](fundamentals/multilocation/README.md) + - [Junction(s)](fundamentals/multilocation/junction.md) + - [Example](fundamentals/multilocation/example.md) - [MultiAsset](fundamentals/multiasset.md) - - [Responses]() - - [Fees]() + - [Responses](fundamentals/responses.md) + - [Fees](fundamentals/fees.md) - [A Journey through XCM]() - [Transfers]() - [Fees]() diff --git a/src/fundamentals/README.md b/src/fundamentals/README.md index e69de29..5d6b6bd 100644 --- a/src/fundamentals/README.md +++ b/src/fundamentals/README.md @@ -0,0 +1,6 @@ +# Fundamentals +In this chapter we explore all the fundamentals that you should understand before diving deeper into XCM: +- [MultiLocation](./multilocation/index.md) +- [MultiAsset](./multiasset.md) +- [Responses](./responses.md) +- [Fees](./fees.md) \ No newline at end of file diff --git a/src/fundamentals/fees.md b/src/fundamentals/fees.md new file mode 100644 index 0000000..d39c07d --- /dev/null +++ b/src/fundamentals/fees.md @@ -0,0 +1 @@ +Todo \ No newline at end of file diff --git a/src/fundamentals/images/MultiLocation_Example.png b/src/fundamentals/images/MultiLocation_Example.png new file mode 100644 index 0000000000000000000000000000000000000000..e234a867ebfd5f89badf7f271788acf874163e32 GIT binary patch literal 45421 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 literal 0 HcmV?d00001 diff --git a/src/fundamentals/images/MultiLocation_simple_example.png b/src/fundamentals/images/MultiLocation_simple_example.png new file mode 100644 index 0000000000000000000000000000000000000000..c498bd6b4b9fd250a0df3e8bd216694e07a9a170 GIT binary patch literal 16373 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 M72V5)O4q{vA7{Y#^Z)<= literal 0 HcmV?d00001 diff --git a/src/fundamentals/multiasset.md b/src/fundamentals/multiasset.md index bad7d1f..d3d86e1 100644 --- a/src/fundamentals/multiasset.md +++ b/src/fundamentals/multiasset.md @@ -1 +1,126 @@ +# MultiAsset +When working in XCM it’s often needed to refer to an asset of some sort. This is because practically all public blockchains in existence rely on some native digital asset to provide the backbone for its internal economy and security mechanism. For example, the native asset for the Polkadot relay chain is DOT. + +Some blockchains manage multiple assets, e.g. Ethereum’s ERC-20 framework allows for many different assets to be managed on-chain. Some manage assets that are not fungible such as Ethereum’s ETH but rather are non-fungible — one-of-a-kind instances; Crypto-kitties was an early example of such non-fungible tokens or NFTs. + +XCM is designed to be able to handle all such assets without breaking a sweat. For this purpose, there is the datatype `MultiAsset` together with its associated types `MultiAssets`, `WildMultiAsset`, and `MultiAssetFilter`. + +## MultiAsset Breakdown +Let's take a look at the MultiAsset struct: +```rust,noplayground +pub struct MultiAsset { + pub id: AssetId, + pub fun: Fungibility, +} +``` + +So two fields define our asset: id and fun, this is pretty indicative of how XCM approaches assets. Firstly, an overall asset identity must be provided. For fungible assets, this simply identifies the asset. For NFTs this identifies the overall asset “class” — different asset instances may be within this class. + +```rust,noplayground +enum AssetId { + Concrete(MultiLocation), + Abstract([u8; 32]), +} +``` + +The asset identity is expressed in one of two ways; either Concrete or Abstract. Abstract is not really in use, but it allows asset IDs to be specified by name. This is convenient but relies on the receiver interpreting the name in the way that the sender expects which may not always be so easy. Concrete is in general usage and uses a `MultiLocation` to identify an asset unambiguously. For native assets (such as DOT), the asset tends to be identified as the chain which mints the asset (the Polkadot Relay Chain in this case, which would be the location `..` from one of its parachains). Assets that are primarily administered within a chain’s pallet may be identified by a location including their index within that pallet. For some examples of `MultiAsset`s see the example section below. + +```rust,noplayground +enum Fungibility { + // Fungible cannot be 0 + Fungible(u128), + NonFungible(AssetInstance), +} +``` +Secondly, they must be either fungible or non-fungible. If they’re fungible, then there should be some associated non-zero amount. If they’re not fungible, then instead of an amount, there should be some indication of which [AssetInstance](https://paritytech.github.io/polkadot/doc/xcm/v3/enum.AssetInstance.html) they are. (This is commonly expressed with an index, but XCM also allows arrays.) + + +## How to use Multiple Assets Together? +There are multiple ways to group Assets. In this section, we go over these methods. + +### MultiAssets +One way to group a set of `MultiAsset` items is the [MultiAssets](https://paritytech.github.io/polkadot/doc/xcm/v3/struct.MultiAssets.html) type. It is a `Vec` of `MultiAsset` items. + +```rust,noplayground +struct MultiAssets(Vec); +``` + +This structure must uphold some rules: +- It may contain no items of duplicate asset class; +- All items must be ordered; +- The number of items should grow no larger than MAX_ITEMS_IN_MULTIASSETS (currently set to 20). + + + +### WildMultiAsset +Then we have WildMultiAsset; this is a wildcard that can be used to match against one or more MultiAsset items. +All the WildMultiAsset wildcards describe the assets in the [Holding register](../overview/architecture.md). + +```rust,noplayground +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, + }, +} +``` + +### MultiAssetFilter +Finally, there is `MultiAssetFilter`. This is used most often and is just a combination of MultiAssets and WildMultiAsset allowing either a wildcard or a list of definite (i.e. not wildcard) assets to be specified. + +```rust,noplayground +pub enum MultiAssetFilter { + /// Specify the filter as being everything contained by the given `MultiAssets` inner. + Definite(MultiAssets), + /// Specify the filter as the given `WildMultiAsset` wildcard. + Wild(WildMultiAsset), +} +``` + +## Examples ### MultiAsset +For more information about the MultiLocations used to define concrete assets, see [MultiLocation](multilocation/README.md) and [Junction](multilocation/junction.md). +```rust,noplayground +// Location Relay Chain +// 100 Native Asset (three ways) +MultiAsset {id: Concrete(MultiLocation {parents: 0, interior: Here}), fun: Fungible(100u128)}; +MultiAsset {id: Here.into(), fun: 100.into()}; +(Here, 100).into(); + +// 100 Parachain's Native Asset +(X1(Parachain(1000)), 100).into(); +// 100 Fungible assets in Parachain 1000 with id 1234 +(X2(Parachain(1000), GeneralIndex(1234)), 100).into(); +// Non Fungible asset with asset class 1234 containing only one nft instance in Parachain 1000 +(X2(Parachain(1000), GeneralIndex(1234)), Undefined).into(); +// Non Fungible asset with asset class 1234 and AssetInstance 1 in Parachain 1000 +(X2(Parachain(1000), GeneralIndex(1234)), Index(1)).into(); +``` + +### MultiAssetFilter + +```rust,noplayground +let a1: MultiAssets = MultiAssets::from(vec![MultiAsset {id: Here.into(), fun: 100.into()}]); +let b1: MultiAssets = (Here, 100).into(); +assert_eq!(a1, b1); + +let a2: MultiAssetFilter = a1.into(); +let b2 = MultiAssetFilter::Definite((Here, 100).into()); +assert_eq!(a2, b2); + +let a3 = MultiAssetFilter::Wild(WildMultiAsset::All); +let b3: MultiAssetFilter = All.into(); +assert_eq!(a3, b3); +``` + diff --git a/src/fundamentals/multilocation.md b/src/fundamentals/multilocation.md deleted file mode 100644 index 6d788c1..0000000 --- a/src/fundamentals/multilocation.md +++ /dev/null @@ -1,6 +0,0 @@ -### MultiLocation - - - - -- functions on multilocation https://paritytech.github.io/polkadot/doc/xcm/v3/struct.MultiLocation.html \ No newline at end of file diff --git a/src/fundamentals/multilocation/README.md b/src/fundamentals/multilocation/README.md new file mode 100644 index 0000000..8183dd2 --- /dev/null +++ b/src/fundamentals/multilocation/README.md @@ -0,0 +1,64 @@ +# MultiLocation +The [MultiLocation](https://paritytech.github.io/polkadot/doc/xcm/v3/struct.MultiLocation.html) type identifies any single location that exists within the world of consensus. It is quite an abstract idea and can represent all manner of things that exist within consensus, from a scalable multi-shard blockchain such as Polkadot down to a lowly ERC-20 asset account on a parachain. MultiLocations are used to identify places to send XCM messages, places that can receive assets, and then can even help describe the type of an asset itself, as we will see in [MultiAsset](../multiasset.md). + +### Location is relative +MultiLocation always expresses a location relative to the current location. You can think of it a bit like a file system path but where there is no way of directly expressing the “root” of the file system tree. This is for a simple reason: In the world of Polkadot, blockchains can be merged into, and split from other blockchains. A blockchain can begin life very much alone, and eventually be elevated to become a parachain within a larger consensus. If it did that, then the meaning of “root” would change overnight and this could spell chaos for XCM messages and anything else using MultiLocation. To keep things simple, we exclude this possibility altogether. + +### Hierarchical structure +Locations in XCM are hierarchical; some places in consensus are wholly encapsulated within other places in consensus. A parachain of Polkadot exists wholly within the overall Polkadot consensus and we call it an interior location. Or a pallet exists wholly within a parachain or relaychain. Putting it more strictly, we can say that whenever there is a consensus system any change in which implies a change in another consensus system, then the former system is interior to the latter. + +### So what is a MultiLocation: Simple example +A quick summary of the previous points: +- A MultiLocation identifies any single location that exists within the world of consensus. +- A MultiLocation is always relative to the current location. +- MultiLocations in XCM are hierarchical. + +Now take a look at the MultiLocation struct: +```rust,noplayground +pub struct MultiLocation { + pub parents: u8, + pub interior: Junctions, +} +``` +As we have already discussed, locations in XCM are hierarchical. The following image shows an example of such a Hierarchy. + +![Simple Example](./../images/MultiLocation_simple_example.png) + +Relaychain A completely encapsulates Parachain A and B (indicated by the arrows) and parachain A encapsulates an account `0x00...`. So RelayA is higher in the hierarchy than ParaA and ParaB and can be described as the `parent` of these parachains. So the `parents: u8` in the MultiLocation struct describes the number of steps in the hierarchy we want to move up. The `interior: Junctions` express the steps in the hierarchy we want to move down. The `Junctions` type will be further discussed in the next chapter about [Junctions](junction.md), but for now, it's just a way to express a way down the hierarchy. As all MultiLocations are relative to the current location, Parachain B relative to Parachain A is one step up and one step down in the hierarchy. + +To get a better understanding of this concept, we show some simple MultiLocations in the code example below. The first two examples are relative to RelayA and the second set of examples is relative to ParaB. In the `Location` comments, we expressed the locations in text. The `..` express a step up in the hierarchical structure (the “parent” or the encapsulating consensus system). The `..` are followed by some number of [Junctions](junction.md), all separated by `/`. + +```rust,noplayground +// From: RelayA +// To: ParaB +// Location: /Parachain(2000) +MultiLocation {parents: 0, interior: X1(Parachain(2000))}; +// To: Account in ParaA +// Location: /Parachain(1000)/AccountId32(0x00..) +MultiLocation { + parents: 0, + interior: X2( + Parachain(1000), + AccountId32{network: None, id: [0u8; 32]} + ) +}; + +// From: ParaB +// To: RelayA +// Location: ../here +MultiLocation {parents: 1, interior: Here}; +// To: Account in ParaA +// Location: ../Parachain(1000)/AccountId32(0x00..) +MultiLocation { + parents: 1, + interior: X2( + Parachain(1000), + AccountId32{network: None, id: [0u8; 32]} + ) +}; +``` + +## What's next: +- More information about [junctions](junction.md) +- More MultiLocation [examples](example.md) +- Expressing assets using Multilocations: [MultiAsset][../multiasset.md] \ No newline at end of file diff --git a/src/fundamentals/multilocation/example.md b/src/fundamentals/multilocation/example.md new file mode 100644 index 0000000..213add0 --- /dev/null +++ b/src/fundamentals/multilocation/example.md @@ -0,0 +1,8 @@ +# Example +In this example we show different `MultiLocation`s for the consensus hierarchy in the image below. +![Example](./../images/MultiLocation_Example.png) + +```rust,noplayground +// Todo: Make MultiLocations + +``` diff --git a/src/fundamentals/multilocation/junction.md b/src/fundamentals/multilocation/junction.md new file mode 100644 index 0000000..c34e53a --- /dev/null +++ b/src/fundamentals/multilocation/junction.md @@ -0,0 +1,172 @@ +# Junction(s) +In the section on [MultiLocations](README.md), we looked at the MultiLocation struct. We talked about the Multilocation being a way to describe moving from one place in the consensus hierarchy to another. The `parents` parameter expresses the number of steps up in the hierarchy. In this section, we dive further into the MultiLocation struct and explain how we can use the Junctions type to describe steps in the consensus hierarchy. +Take a look at the MultiLocation struct again: + +```rust,noplayground +pub struct MultiLocation { + pub parents: u8, + pub interior: Junctions, +} +``` + +The consensus hierarchy consists of 1-to-n relations. Each place in the consensus hierarchy has one parent, so there is only one way up the hierarchy. That is why we can use a `u8` to describe the number of `parents` we want to move up. But moving down is a bit more difficult, as one consensus system can encapsulate multiple other consensus systems(e.g. a relaychain can have multiple parachains). So to describe the correct steps down the hierarchy, we use the `Junctions` [type](https://paritytech.github.io/polkadot/doc/xcm/v3/enum.Junctions.html). + + +## Junctions Type +```rust,noplayground +pub enum Junctions { + /// The interpreting consensus system. + Here, + /// A relative path comprising 1 junction. + X1(Junction), + ... + /// A relative path comprising 8 junctions. + X8(Junction, Junction, Junction, Junction, Junction, Junction, Junction, Junction), +} +``` +The `Junctions` enum can represent zero to eight steps down the hierarchy. When the `Here` junction is used, it means that we do not have to take steps down the hierarchy. We can for example describe the current location with `{parents: 0, interior: Here}` or the Parent location with `{parents: 1, interior: Here}`. If we want to take steps down the hierarchy, we express each step as a Junction. + +## Junction Type +A [Junction](https://paritytech.github.io/polkadot/doc/xcm/v3/enum.Junction.html) describes a step down in the Hierarchy. The `Junction`s are defined as follows: + +```rust,noplayground +pub enum Junction { + Parachain(u32), + AccountId32 { + network: Option, + id: [u8; 32], + }, + AccountIndex64 { + network: Option, + index: u64, + }, + AccountKey20 { + network: Option, + key: [u8; 20], + }, + PalletInstance(u8), + GeneralIndex(u128), + GeneralKey { + length: u8, + data: [u8; 32], + }, + OnlyChild, + Plurality { + id: BodyId, + part: BodyPart, + }, + GlobalConsensus(NetworkId), +} +``` + +#### Parachain +The `Parachain` junction is used to describe a parachain from the point of a relaychain. Each parachain has an Id, e.g. statemine in the Kusama network has Id 1000. + +#### PalletInstance +The `PalletInstance` junction is used to describe a pallet in one of the parachains or relaychain. Each pallet has an Id that can be used for the `PalletInstance` + +#### AccountId32 and AccountKey20 +Each of these junctions can be used to describe an account located in the current consensus system. The `AccountId32` is used to describe substrate-based accounts, while the `AccountKey20` is mainly used to describe Ethereum or Bitcoin-based accounts or smart contracts. Both junctions express an account based on the context they are used in. If the current location is the Relaychain, then the junctions describe an account in the relaychain. The same is true for each parachain location. + +#### GeneralIndex and GeneralKey +Non-descript indices and keys within the current context location. The usage will vary widely owing to its generality. An example use case for the `GeneralIndex` is to describe an Asset within an Assets Parachain. + +NOTE: Try to avoid using this and instead use a more specific item. + +#### AccountIndex64 +The `AccountIndex64` can be used to describe an account index for the Indices Pallet. + +#### OnlyChild +The `OnlyChild` junction can be used to describe the child of a location if there exists a 1-to-1 relation between the parent and child in the consensus hierarchy. The `OnlyChild` junction is currently not used except as a fallback when deriving context. + +#### Plurality +The `Plurality` junction is used to describe a pluralistic body existing within the current consensus location. +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. See the [BodyId documentation](https://paritytech.github.io/polkadot/doc/xcm/v3/enum.BodyId.html) for a better understanding of the bodies that the `Plurality` junction can represent. + +#### GlobalConsensus +A global network (e.g. Polkadot or Kusama) is capable of externalizing its own consensus. This is not generally meaningful outside of the universal level. An example would be describing the Kusama relaychain from the perspective of the Polkadot relaychain as `{parents: 1, interior: GlobalConsensus(Kusama)}`. An example use case could be routing XCM messages between global consensus networks using bridges. + +## Multiple ways to create a MultiLocation +```rust,noplayground +// Current Location +MultiLocation {parents: 0, interior: Here}; +MultiLocation::new(0, Here); +MultiLocation::here(); +MultiLocation::default(); +Here.into(); + +// Parent Location +MultiLocation {parents: 1, interior: Here}; +MultiLocation::parent(); +Parent.into(); + +// Conversion +MultiLocation { parents: 2, interior: X2(Parachain(1), GeneralIndex(1))}; +(Parent, Parent, Parachain(1), GeneralIndex(1)).into(); +```# MultiLocation +The [MultiLocation](https://paritytech.github.io/polkadot/doc/xcm/v3/struct.MultiLocation.html) type identifies any single location that exists within the world of consensus. It is quite an abstract idea and can represent all manner of things that exist within consensus, from a scalable multi-shard blockchain such as Polkadot down to a lowly ERC-20 asset account on a parachain. MultiLocations are used to identify places to send XCM messages, places that can receive assets, and then can even help describe the type of an asset itself, as we will see in [MultiAsset](../multiasset.md). + +### Location is relative +MultiLocation always expresses a location relative to the current location. You can think of it a bit like a file system path but where there is no way of directly expressing the “root” of the file system tree. This is for a simple reason: In the world of Polkadot, blockchains can be merged into, and split from other blockchains. A blockchain can begin life very much alone, and eventually be elevated to become a parachain within a larger consensus. If it did that, then the meaning of “root” would change overnight and this could spell chaos for XCM messages and anything else using MultiLocation. To keep things simple, we exclude this possibility altogether. + +### Hierarchical structure +Locations in XCM are hierarchical; some places in consensus are wholly encapsulated within other places in consensus. A parachain of Polkadot exists wholly within the overall Polkadot consensus and we call it an interior location. Or a pallet exists wholly within a parachain or relaychain. Putting it more strictly, we can say that whenever there is a consensus system any change in which implies a change in another consensus system, then the former system is interior to the latter. + +### So what is a MultiLocation: Simple example +A quick summary of the previous points: +- A MultiLocation identifies any single location that exists within the world of consensus. +- A MultiLocation is always relative to the current location. +- MultiLocations in XCM are hierarchical. + +Now take a look at the MultiLocation struct: +```rust,noplayground +pub struct MultiLocation { + pub parents: u8, + pub interior: Junctions, +} +``` +As we have already discussed, locations in XCM are hierarchical. The following image shows an example of such a Hierarchy. + +![Simple Example](./../images/MultiLocation_simple_example.png) + +Relaychain A completely encapsulates Parachain A and B (indicated by the arrows) and parachain A encapsulates an account `0x00...`. So RelayA is higher in the hierarchy than ParaA and ParaB and can be described as the `parent` of these parachains. So the `parents: u8` in the MultiLocation struct describes the number of steps in the hierarchy we want to move up. The `interior: Junctions` express the steps in the hierarchy we want to move down. The `Junctions` type will be further discussed in the next chapter about [Junctions](junction.md), but for now, it's just a way to express a way down the hierarchy. As all MultiLocations are relative to the current location, Parachain B relative to Parachain A is one step up and one step down in the hierarchy. + +To get a better understanding of this concept, we show some simple MultiLocations in the code example below. The first two examples are relative to RelayA and the second set of examples is relative to ParaB. In the `Location` comments, we expressed the locations in text. The `..` express a step up in the hierarchical structure (the “parent” or the encapsulating consensus system). The `..` are followed by some number of [Junctions](junction.md), all separated by `/`. + +```rust,noplayground +// From: RelayA +// To: ParaB +// Location: /Parachain(2000) +MultiLocation {parents: 0, interior: X1(Parachain(2000))}; +// To: Account in ParaA +// Location: /Parachain(1000)/AccountId32(0x00..) +MultiLocation { + parents: 0, + interior: X2( + Parachain(1000), + AccountId32{network: None, id: [0u8; 32]} + ) +}; + +// From: ParaB +// To: RelayA +// Location: ../here +MultiLocation {parents: 1, interior: Here}; +// To: Account in ParaA +// Location: ../Parachain(1000)/AccountId32(0x00..) +MultiLocation { + parents: 1, + interior: X2( + Parachain(1000), + AccountId32{network: None, id: [0u8; 32]} + ) +}; +``` + +## What's next: +- More information about [junctions](junction.md) +- More MultiLocation [examples](example.md) +- Expressing assets using Multilocations: [MultiAsset][../multiasset.md] + + diff --git a/src/fundamentals/responses.md b/src/fundamentals/responses.md new file mode 100644 index 0000000..d39c07d --- /dev/null +++ b/src/fundamentals/responses.md @@ -0,0 +1 @@ +Todo \ No newline at end of file From 932882e575c2dc2cdb9aaa1d51b3f2b53ffdf8b3 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Wed, 19 Apr 2023 17:23:33 +0200 Subject: [PATCH 09/73] address feedback --- src/quickstart/README.md | 6 +++--- src/quickstart/first-look.md | 27 +++++++++++++-------------- src/quickstart/xcm-simulator.md | 2 +- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/quickstart/README.md b/src/quickstart/README.md index 0bad600..259fe46 100644 --- a/src/quickstart/README.md +++ b/src/quickstart/README.md @@ -2,12 +2,12 @@ The XCM code can be found in [polkadot repository](https://github.com/paritytech/polkadot/tree/master/xcm). -### Rust & Cargo +## Rust & Cargo A pre-requisite for using XCM is to have a stable Rust version and Cargo installed. Here's an [installation guide](https://docs.substrate.io/install/) on how to install rust and cargo. -### Running the Examples +## Running the Examples -All examples in the documentation are located in the [examples repository](). Follow the following steps to run the `first-look` example. First clone the repository: +All examples in the documentation are located in the [examples repository](). Follow these steps to run the `first-look` example. First clone the repository: ```shell git clone git@github.com:vstam1/xcm-examples.git diff --git a/src/quickstart/first-look.md b/src/quickstart/first-look.md index 21435c0..38c1ea6 100644 --- a/src/quickstart/first-look.md +++ b/src/quickstart/first-look.md @@ -1,7 +1,6 @@ -## First Look +# First Look In this section, we take you through a simple example of an XCM. In this example, we withdraw the native token from the account of Alice and deposit this token in the account of Bob. This message simulates a transfer between two accounts in the same consensus system (`ParaA`). Find here the [code example](). - -### Message +## Message ```rust let message = Xcm(vec![ WithdrawAsset((Here, amount).into()), @@ -10,17 +9,17 @@ In this section, we take you through a simple example of an XCM. In this example assets: All.into(), beneficiary: MultiLocation { parents: 0, - interior: X1(Junction::AccountId32 { - network: Some(NetworkId::Kusama), + interior: Junction::AccountId32 { + network: None, id: BOB.clone().into() - }), + }.into(), }.into() } ]); ``` The message consists of three instructions: WithdrawAsset, BuyExecution, and DepositAsset. In the following sections we will go over each of these instructions. -##### WithdrawAsset +### WithdrawAsset ```rust WithdrawAsset((Here, amount).into()) ``` @@ -28,22 +27,22 @@ WithdrawAsset((Here, amount).into()) The first instruction takes as an input the [MultiAsset]() that should be withdrawn. The MultiAsset describes the native parachain token with the `Here` keyword. The `amount` parameter is the number of tokens that are transferred. The withdrawal account depends on the Origin of the message. In this example the Origin of the message is Alice. The WithdrawAsset instruction moves `amount` number of native tokens from Alice's account into the `Holding register`. -##### BuyExecution +### BuyExecution ```rust BuyExecution{fees: (Here, amount).into(), weight_limit: WeightLimit::Unlimited} ``` -To execute XCM instructions, weight has to be bought. The amount of weight depends on the number and type of instructions in the XCM. The `BuyExecution` instruction pays for the weight using the `fees`. The `fees` parameter describes the asset in the `Holding register` that should be used for paying for the weight. The `weight_limit` defines the maximum amount of fees that can be used for buying weight. See [fees]() for more information about the fees in XCM. +To execute XCM instructions, weight (some kind of resources) has to be bought. The amount of weight depends on the number and type of instructions in the XCM. The `BuyExecution` instruction pays for the weight using the `fees`. The `fees` parameter describes the asset in the `Holding register` that should be used for paying for the weight. The `weight_limit` defines the maximum amount of fees that can be used for buying weight. There are special occasions where it is not necessary to buy weight. See [fees]() for more information about the fees in XCM. -##### DepositAsset +### DepositAsset ```rust DepositAsset { assets: All.into(), beneficiary: MultiLocation { parents: 0, - interior: X1(Junction::AccountId32 { - network: Some(NetworkId::Kusama), + interior: Junction::AccountId32 { + network: None, id: BOB.clone().into() - }), + }.into(), }.into() } ``` @@ -52,7 +51,7 @@ The DepositAsset instruction is used to deposit funds from the holding register When the three instructions are combined, we withdraw `amount` native tokens from the account of Alice, pay for the execution of the instructions, and deposit the remaining tokens in the account of Bob. -##### What next? +## What next? Now that we have taken a first look at an XCM, we can dive deeper into all the XCM instructions. For an overview of the instructions check out the [xcm-format](https://github.com/paritytech/xcm-format#5-the-xcvm-instruction-set). Or check out examples for each of the instruction in [A Journey through XCM](). diff --git a/src/quickstart/xcm-simulator.md b/src/quickstart/xcm-simulator.md index 1620384..178bb77 100644 --- a/src/quickstart/xcm-simulator.md +++ b/src/quickstart/xcm-simulator.md @@ -1,4 +1,4 @@ -### xcm-simulator +# XCM Simulator Setting up a live network with multiple connected parachains for testing XCM is not straight forward. The `XCM-simulator` was created as a solution to this problem. The XCM-simulator is a network simulator specifically designed for testing and playing around with XCM. It uses mock relay chain and parachain runtime. For testing xcm configurations for live runtime environments we use the `XCM-emulator`. The XCM-emulator can use production relay chain and parachain runtimes. Users can plug in Kusama, Statemine, or their custom runtime etc. With up-to-date chain specs, it's able to verify if specific XCM messages work in live networks. The specific use cases will be further explained in the chapter on [testing](testing/README.md). From 82fe726fd982355e2a720d3d3462c100db556db7 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 19 Apr 2023 13:36:36 -0300 Subject: [PATCH 10/73] Add overview (draft) (#3) * Add overview * Address feedback * Add holding register to overview XCVM * Add more links --- src/SUMMARY.md | 11 ++++++---- src/overview.md | 1 - src/overview/README.md | 16 ++++++++++++++ src/overview/architecture.md | 36 ++++++++++++++++++++++++++++++++ src/overview/format.md | 17 +++++++++++++++ src/overview/interoperability.md | 21 +++++++++++++++++++ src/overview/xcvm.md | 23 ++++++++++++++++++++ src/xcm.md | 5 +++++ 8 files changed, 125 insertions(+), 5 deletions(-) delete mode 100644 src/overview.md create mode 100644 src/overview/README.md create mode 100644 src/overview/architecture.md create mode 100644 src/overview/format.md create mode 100644 src/overview/interoperability.md create mode 100644 src/overview/xcvm.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index a5fc430..26ade73 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -2,10 +2,11 @@ [XCM: Cross-Consensus Messaging](xcm.md) -- [Overview]() - - [The Promise of Interoperability]() - - [A Format, not a Protocol]() - - [Architecture]() +- [Overview](overview/README.md) + - [Introduction](overview/interoperability.md) + - [A Format, not a Protocol](overview/format.md) + - [The XCVM](overview/xcvm.md) + - [Architecture](overview/architecture.md) - [Quickstart]() - [XCM Simulator]() - [First Look at an XCM]() @@ -24,6 +25,7 @@ - [Channels]() - [When all else fails]() - [Misc]() +- [Config Deep Dive]() - [Testing]() - [Separation of concerns]() - [Simulating message execution]() @@ -38,5 +40,6 @@ - [Cookbook]() - [All Instructions]() +- [All XCVM Registers]() [Next Steps]() diff --git a/src/overview.md b/src/overview.md deleted file mode 100644 index 07dd0c5..0000000 --- a/src/overview.md +++ /dev/null @@ -1 +0,0 @@ -# Overview diff --git a/src/overview/README.md b/src/overview/README.md new file mode 100644 index 0000000..0223581 --- /dev/null +++ b/src/overview/README.md @@ -0,0 +1,16 @@ +# Overview + +XCM allows for different consensus systems to communicate with each other. +This allows things like: +- Sending tokens from one chain to another +- Locking assets on one chain in order to gain some benefit on a smart contract on another chain +- Calling extrinsics on another chain + +But that's just the beginning. +The true power of XCM comes from its composability. +Once you can communicate with other consensus systems, you can get creative and implement whatever use case you need. +This is especially true in the context of an ecosystem of highly specialized chains, like Polkadot. + +In this chapter, we will cover what XCM is, what it isn't, why it matters, and delve into the different components that make up the XCM ecosystem. + +Let's begin. diff --git a/src/overview/architecture.md b/src/overview/architecture.md new file mode 100644 index 0000000..bf435ce --- /dev/null +++ b/src/overview/architecture.md @@ -0,0 +1,36 @@ +# Architecture + +XCM is a [format](https://github.com/paritytech/xcm-format), which means anyone is free to create an implementation for it. +The first one is made in [Rust](https://www.rust-lang.org/), primarily for [Substrate](https://substrate.io/)-based chains in the [Polkadot](https://polkadot.network/) ecosystem. +We'll be looking at this first implementation to tinker with different types of messages in the next sections. +For now, we'll take a look at how it's structured. + +All the code lives in the [Polkadot repo](https://github.com/paritytech/polkadot/tree/master/xcm). +The main structure is as follows: +- XCM: Defines the fundamental constructs used in XCM and an enum with all the instructions available. +- Executor: Implements the XCVM, capable of executing XCMs. Highly configurable. +- Builder: Offers common configuration building blocks for the executor. +- Pallet: FRAME pallet that provides extrinsics with specific XCM programs. +- Simulator: Allows for testing of XCM programs. + +## Executor + +The XCM executor is responsible for interpreting and executing XCM messages. +It is the core engine that processes and handles XCM instructions, ensuring that they are carried out accurately and in the correct order. +The XCM executor follows the Cross-Consensus Virtual Machine (XCVM) specification and can be extended, customized, or even replaced with an alternative construct that adheres to the XCVM spec. + +## Builder + +The XCM executor is highly configurable. +XCM builder provides building blocks people can use to configure their executor according to their needs. + +## Pallet + +The XCM pallet is a FRAME pallet that can be used to execute XCMs locally or send them to a different system. +It also has extrinsics for specific use cases such as teleporting assets or doing reserve asset transfers, which we'll talk about later. +It's the glue between XCM and FRAME, which is highly used in the Polkadot ecosystem. + +## Simulator + +The simulator allows for testing XCMs fast, without needing to boot up several different nodes in a network, or test in production. +It's a very useful tool which we'll use later to build and test different XCMs. diff --git a/src/overview/format.md b/src/overview/format.md new file mode 100644 index 0000000..d980703 --- /dev/null +++ b/src/overview/format.md @@ -0,0 +1,17 @@ +# A Format, Not a Protocol + +It's essential to understand that XCM is a format, not a protocol. +It describes how messages should be structured and contains instructions relevant to the on-chain actions the message intends to perform. +However, XCM does not dictate how messages are delivered. +That responsibility falls on [transport layer protocols](TODO:link) such as XCMP (Cross Chain Message Passing) and VMP (Vertical Message Passing) in the Polkadot ecosystem, or any others to come. + +XCM is similar to how RESTful services use REST as an architectural style of development, where HTTP requests contain specific parameters to perform some action. +Similar to UDP, out of the box XCM is a "fire and forget" model, unless there is a separate XCM message designed to be a response message which can be sent from the recipient to the sender. All error handling should also be done on the recipient side. + +XCM is not designed in a way where every system supporting the format is expected to be able to interpret any possible XCM message. Practically speaking, one can imagine that some messages will not have reasonable interpretations under some systems or will be intentionally unsupported. + +Furthermore, it's essential to realize that XCM messages by themselves are not considered transactions. XCM describes how to change the state of the target network, but the message by itself doesn't perform the state change. +This partly ties to what is called asynchronous composability, which allows XCM messages to bypass the concept of time-constrained mechanisms, like on-chain scheduling and execution over time in the correct order in which it was intended. + +XCM is a language in which rich interactions between systems can be written. +Both simple and more complex scenarios can be expressed, and developers are encouraged to design and implement diverse cross-consensus communication solutions. diff --git a/src/overview/interoperability.md b/src/overview/interoperability.md new file mode 100644 index 0000000..7b8ed15 --- /dev/null +++ b/src/overview/interoperability.md @@ -0,0 +1,21 @@ +# Introduction + +XCM is a messaging format, a language, designed to enable seamless communication between different consensus systems, for example blockchains and smart contracts. +XCM was originally developed for the [Polkadot](https://polkadot.network/) ecosystem, but was designed to provide a common language for cross-consensus communication that can be used anywhere. + +XCM is a language in which interactions (programs) can be written. +It aims to provide better interoperability between consensus systems, both more features and a better user and developer experience. + +Its goal is to let blockchain ecosystems thrive via specialization instead of generalization. +If there's no interoperability, a chain is forced to do everything on its own. +With XCM, a chain can specialize and do what it does best, while still getting the benefits from interacting with others. + +XCM has four high-level core design principles which it stands to follow: +1. Asynchronous: XCM messages in no way assume that the sender will be blocking on its completion +2. Absolute: XCM messages are guaranteed to be delivered and interpreted accurately, in order and in a timely fashion. Once a message is sent, one can be sure it will be processed as it was intended to be. +3. Asymmetric: XCM messages, by default, do not have results that let the sender know that the message was received - they follow the 'fire and forget' paradigm. Any results must be separately communicated to the sender with an additional message back to the origin. +4. Agnostic: XCM makes no assumptions about the nature of the consensus systems between which the messages are being passed. XCM as a message format should be usable in any system that derives finality through consensus. + +XCM is a work-in-progress, the format is expected to change over time. +It has an RFC process to propose changes, which end up in newer versions, the current one being v3. +To keep up with the development of the format, or to propose changes, go to [the XCM format repository](https://github.com/paritytech/xcm-format). diff --git a/src/overview/xcvm.md b/src/overview/xcvm.md new file mode 100644 index 0000000..ec89f96 --- /dev/null +++ b/src/overview/xcvm.md @@ -0,0 +1,23 @@ +# The XCVM + +At the core of XCM lies the XCVM (Cross-Consensus Virtual Machine). +A message in XCM (referred to as an XCM, cross-consensus message, or XCMs for more than one) is an XCVM program. +The XCVM is a register-based state machine that executes every program by processing its instructions one at a time. +During execution, state is tracked in domain-specific registers, and is constantly being used and updated. +Most of the XCM format comprises these registers and the instructions used to compose XCVM programs. + +Like XCM, the XCVM is also a specification. +The first implementation is [xcm-executor](https://github.com/paritytech/polkadot/tree/master/xcm/xcm-executor), provided by Parity. +It's built to be highly configurable, with its building blocks available in [xcm-builder](https://github.com/paritytech/polkadot/tree/master/xcm/xcm-builder). +Configuring the executor is an important and extensive topic, one we will dive into further in [Config Deep Dive](TODO:link). +It's entirely possible to create another implementation of the XCVM if desired. + +Typically, an XCM takes the following path through the XCVM: +- Instructions within an XCM are read one-by-one by the XCVM. An XCM may contain one or more instructions. +- The instruction is executed. This means that the current values of the XCVM registers, the instruction type, and the instruction operands are all used to execute some operation, which might result in some registers changing their value, or in an error being thrown, which would halt execution. +- Each subsequent instruction within the XCM is read until the end of the message has been reached. + +The XCVM register you will hear most about is the `Holding` register. +An XCVM program that handles assets (which means most of them) will be putting them in and taking them out of this register. +Instructions we'll see later like `DepositAsset`, `WithdrawAsset` and many more, make use of this register. +You can see all registers in the [All XCVM Registers](TODO:link) section. diff --git a/src/xcm.md b/src/xcm.md index 819a650..fd5f0e9 100644 --- a/src/xcm.md +++ b/src/xcm.md @@ -2,3 +2,8 @@ This is the place where we will brainstorm XCM documentation and tutorial ideas. It will eventually be in a public centralized place for everyone to find and use. + +## Draft intro + +Welcome to the Cross-Consensus Message Format (XCM) documentation! +Whether you're a developer, a blockchain enthusiast, or just interested in Polkadot, this guide aims to provide you with an easy-to-understand and comprehensive introduction to XCM. From b0bfb898254bce81c8f5aa1b103720aa284e829b Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Thu, 20 Apr 2023 10:43:36 +0200 Subject: [PATCH 11/73] address feedback --- src/fundamentals/multiasset.md | 59 ++++++---- src/fundamentals/multilocation/README.md | 43 ++++++-- src/fundamentals/multilocation/example.md | 2 +- src/fundamentals/multilocation/junction.md | 120 +++++++-------------- 4 files changed, 114 insertions(+), 110 deletions(-) diff --git a/src/fundamentals/multiasset.md b/src/fundamentals/multiasset.md index d3d86e1..254d985 100644 --- a/src/fundamentals/multiasset.md +++ b/src/fundamentals/multiasset.md @@ -1,9 +1,14 @@ # MultiAsset -When working in XCM it’s often needed to refer to an asset of some sort. This is because practically all public blockchains in existence rely on some native digital asset to provide the backbone for its internal economy and security mechanism. For example, the native asset for the Polkadot relay chain is DOT. +When working with XCM, it is often needed to represent an asset of some sort. +This is because practically all public blockchains in existence rely on some native digital asset to provide the backbone for its internal economy and security mechanism. +For example, the native asset for the Polkadot relay chain is DOT. -Some blockchains manage multiple assets, e.g. Ethereum’s ERC-20 framework allows for many different assets to be managed on-chain. Some manage assets that are not fungible such as Ethereum’s ETH but rather are non-fungible — one-of-a-kind instances; Crypto-kitties was an early example of such non-fungible tokens or NFTs. +Some blockchains manage multiple assets, e.g. Ethereum’s ERC-20 framework allows for many different assets to be managed on-chain. +Some manage assets that are not fungible, such as Ethereum’s Crypto-kitties — each kitty is a one-of-a-kind instance. +It was an early example of such non-fungible tokens or NFTs. -XCM is designed to be able to handle all such assets without breaking a sweat. For this purpose, there is the datatype `MultiAsset` together with its associated types `MultiAssets`, `WildMultiAsset`, and `MultiAssetFilter`. +XCM is designed to be able to describe all such assets without breaking a sweat. +For this purpose, there is the `MultiAsset` datatype, along with its associated types `MultiAssets`, `WildMultiAsset`, and `MultiAssetFilter` datatypes. ## MultiAsset Breakdown Let's take a look at the MultiAsset struct: @@ -14,7 +19,11 @@ pub struct MultiAsset { } ``` -So two fields define our asset: id and fun, this is pretty indicative of how XCM approaches assets. Firstly, an overall asset identity must be provided. For fungible assets, this simply identifies the asset. For NFTs this identifies the overall asset “class” — different asset instances may be within this class. +So two fields define our asset: id and fun. +These fields are indicative of how XCM approaches assets. +Firstly, an overall asset identity must be provided. +For fungible assets, this is simply a symbol that identifies the asset. +For NFTs this identifies the overall asset “class” — different asset instances may be within this class. ```rust,noplayground enum AssetId { @@ -23,7 +32,12 @@ enum AssetId { } ``` -The asset identity is expressed in one of two ways; either Concrete or Abstract. Abstract is not really in use, but it allows asset IDs to be specified by name. This is convenient but relies on the receiver interpreting the name in the way that the sender expects which may not always be so easy. Concrete is in general usage and uses a `MultiLocation` to identify an asset unambiguously. For native assets (such as DOT), the asset tends to be identified as the chain which mints the asset (the Polkadot Relay Chain in this case, which would be the location `..` from one of its parachains). Assets that are primarily administered within a chain’s pallet may be identified by a location including their index within that pallet. For some examples of `MultiAsset`s see the example section below. +The asset identity is expressed in one of two ways; either Concrete or Abstract. +Abstract identities allow assets to be specified by a 32-byte blob. +This is convenient, but it relies on the receiver to interpret the blob in the way that the sender expects, which will require consensus between the sender and the receiver, and may not be simple to achieve. +Concrete identities uses a `MultiLocation` to identify an asset unambiguously. +For native assets (such as DOT), the asset is identified as the chain which mints the asset (the Polkadot Relay Chain in this case, which would be the location `..` from one of its parachains). +Assets that are primarily administered within a chain’s pallet may be identified by a location including their index within that pallet. ```rust,noplayground enum Fungibility { @@ -32,29 +46,33 @@ enum Fungibility { NonFungible(AssetInstance), } ``` -Secondly, they must be either fungible or non-fungible. If they’re fungible, then there should be some associated non-zero amount. If they’re not fungible, then instead of an amount, there should be some indication of which [AssetInstance](https://paritytech.github.io/polkadot/doc/xcm/v3/enum.AssetInstance.html) they are. (This is commonly expressed with an index, but XCM also allows arrays.) +Secondly, they must be either fungible or non-fungible. +If they’re fungible, then there should be some associated non-zero amount of assets specified. +If they’re not fungible, then instead of an amount, there should be some indication of which [AssetInstance](https://paritytech.github.io/polkadot/doc/xcm/v3/enum.AssetInstance.html) they are. +(This is commonly expressed with an index, but XCM also allows arrays.) ## How to use Multiple Assets Together? -There are multiple ways to group Assets. In this section, we go over these methods. +There are multiple ways to group Assets. +In this section, we go over these methods. ### MultiAssets -One way to group a set of `MultiAsset` items is the [MultiAssets](https://paritytech.github.io/polkadot/doc/xcm/v3/struct.MultiAssets.html) type. It is a `Vec` of `MultiAsset` items. +One way to group a set of `MultiAsset` items is the [MultiAssets](https://paritytech.github.io/polkadot/doc/xcm/v3/struct.MultiAssets.html) type. ```rust,noplayground struct MultiAssets(Vec); ``` This structure must uphold some rules: -- It may contain no items of duplicate asset class; +- It may not contain duplicate `MultiAsset`s (`Fungible` assets are considered the same if their id match. However, `NonFungible` assets are different if the `AssetInstance` is different); - All items must be ordered; - The number of items should grow no larger than MAX_ITEMS_IN_MULTIASSETS (currently set to 20). ### WildMultiAsset -Then we have WildMultiAsset; this is a wildcard that can be used to match against one or more MultiAsset items. -All the WildMultiAsset wildcards describe the assets in the [Holding register](../overview/architecture.md). +Then we have WildMultiAsset; this is a wildcard that can be used to match against one or more MultiAsset items. +All the WildMultiAsset wildcards can be used to select/filter assets in the [Holding register](../overview/xcvm.md). ```rust,noplayground pub enum WildMultiAsset { @@ -77,7 +95,8 @@ pub enum WildMultiAsset { ``` ### MultiAssetFilter -Finally, there is `MultiAssetFilter`. This is used most often and is just a combination of MultiAssets and WildMultiAsset allowing either a wildcard or a list of definite (i.e. not wildcard) assets to be specified. +Finally, there is `MultiAssetFilter`. +This is used most often and is just a combination of MultiAssets and WildMultiAsset allowing either a wildcard or a list of definite (i.e. not wildcard) assets to be specified. ```rust,noplayground pub enum MultiAssetFilter { @@ -96,27 +115,27 @@ For more information about the MultiLocations used to define concrete assets, se // 100 Native Asset (three ways) MultiAsset {id: Concrete(MultiLocation {parents: 0, interior: Here}), fun: Fungible(100u128)}; MultiAsset {id: Here.into(), fun: 100.into()}; -(Here, 100).into(); +let _: MultiAsset = (Here, 100u128).into(); // 100 Parachain's Native Asset -(X1(Parachain(1000)), 100).into(); +let _: MultiAsset = (X1(Parachain(1000)), 100u128).into(); // 100 Fungible assets in Parachain 1000 with id 1234 -(X2(Parachain(1000), GeneralIndex(1234)), 100).into(); +let _: MultiAsset = (X2(Parachain(1000), GeneralIndex(1234)), 100u128).into(); // Non Fungible asset with asset class 1234 containing only one nft instance in Parachain 1000 -(X2(Parachain(1000), GeneralIndex(1234)), Undefined).into(); +let _: MultiAsset = (X2(Parachain(1000), GeneralIndex(1234)), Undefined).into(); // Non Fungible asset with asset class 1234 and AssetInstance 1 in Parachain 1000 -(X2(Parachain(1000), GeneralIndex(1234)), Index(1)).into(); +let _: MultiAsset = (X2(Parachain(1000), GeneralIndex(1234)), Index(1)).into(); ``` ### MultiAssetFilter ```rust,noplayground -let a1: MultiAssets = MultiAssets::from(vec![MultiAsset {id: Here.into(), fun: 100.into()}]); -let b1: MultiAssets = (Here, 100).into(); +let a1: MultiAssets = MultiAssets::from(vec![MultiAsset {id: Here.into(), fun: 100u128.into()}]); +let b1: MultiAssets = (Here, 100u128).into(); assert_eq!(a1, b1); let a2: MultiAssetFilter = a1.into(); -let b2 = MultiAssetFilter::Definite((Here, 100).into()); +let b2 = MultiAssetFilter::Definite((Here, 100u128).into()); assert_eq!(a2, b2); let a3 = MultiAssetFilter::Wild(WildMultiAsset::All); diff --git a/src/fundamentals/multilocation/README.md b/src/fundamentals/multilocation/README.md index 8183dd2..64490ae 100644 --- a/src/fundamentals/multilocation/README.md +++ b/src/fundamentals/multilocation/README.md @@ -1,17 +1,28 @@ # MultiLocation -The [MultiLocation](https://paritytech.github.io/polkadot/doc/xcm/v3/struct.MultiLocation.html) type identifies any single location that exists within the world of consensus. It is quite an abstract idea and can represent all manner of things that exist within consensus, from a scalable multi-shard blockchain such as Polkadot down to a lowly ERC-20 asset account on a parachain. MultiLocations are used to identify places to send XCM messages, places that can receive assets, and then can even help describe the type of an asset itself, as we will see in [MultiAsset](../multiasset.md). +The [MultiLocation](https://paritytech.github.io/polkadot/doc/xcm/v3/struct.MultiLocation.html) type identifies any single location that exists within the world of consensus. +It is quite an abstract idea and can represent all manner of things that exist within consensus, from a scalable multi-shard blockchain such as Polkadot down to a lowly ERC-20 asset account on a parachain. +MultiLocations are used to identify places to send XCM messages, places that can receive assets, and then can even help describe the type of an asset itself, as we will see in [MultiAsset](../multiasset.md). ### Location is relative -MultiLocation always expresses a location relative to the current location. You can think of it a bit like a file system path but where there is no way of directly expressing the “root” of the file system tree. This is for a simple reason: In the world of Polkadot, blockchains can be merged into, and split from other blockchains. A blockchain can begin life very much alone, and eventually be elevated to become a parachain within a larger consensus. If it did that, then the meaning of “root” would change overnight and this could spell chaos for XCM messages and anything else using MultiLocation. To keep things simple, we exclude this possibility altogether. +MultiLocation always expresses a location relative to the current location. +You can think of it a bit like a file system path but where there is no way of directly expressing the “root” of the file system tree. +This is for a simple reason: In the world of Polkadot, blockchains can be merged into, and split from other blockchains. +A blockchain can begin life very much alone, and eventually be elevated to become a parachain within a larger consensus. +If it did that, then the meaning of “root” would change overnight and this could spell chaos for XCM messages and anything else using MultiLocation. +To keep things simple, we exclude this possibility altogether. ### Hierarchical structure -Locations in XCM are hierarchical; some places in consensus are wholly encapsulated within other places in consensus. A parachain of Polkadot exists wholly within the overall Polkadot consensus and we call it an interior location. Or a pallet exists wholly within a parachain or relaychain. Putting it more strictly, we can say that whenever there is a consensus system any change in which implies a change in another consensus system, then the former system is interior to the latter. +Locations in XCM are hierarchical; some places in consensus are wholly encapsulated within other places in consensus. +A parachain of Polkadot exists wholly within the overall Polkadot consensus and we call it an interior location. +Or a pallet exists wholly within a parachain or relay chain. +Putting it more strictly, say we have two consensus systems, A and B. +If any change in A implies a change in B, then we say A is interior to B. ### So what is a MultiLocation: Simple example A quick summary of the previous points: - A MultiLocation identifies any single location that exists within the world of consensus. - A MultiLocation is always relative to the current location. -- MultiLocations in XCM are hierarchical. +- MultiLocations in XCM are hierarchical. Now take a look at the MultiLocation struct: ```rust,noplayground @@ -20,21 +31,33 @@ pub struct MultiLocation { pub interior: Junctions, } ``` -As we have already discussed, locations in XCM are hierarchical. The following image shows an example of such a Hierarchy. +As we have already discussed, locations in XCM are hierarchical. +The following image shows an example of such a Hierarchy. ![Simple Example](./../images/MultiLocation_simple_example.png) -Relaychain A completely encapsulates Parachain A and B (indicated by the arrows) and parachain A encapsulates an account `0x00...`. So RelayA is higher in the hierarchy than ParaA and ParaB and can be described as the `parent` of these parachains. So the `parents: u8` in the MultiLocation struct describes the number of steps in the hierarchy we want to move up. The `interior: Junctions` express the steps in the hierarchy we want to move down. The `Junctions` type will be further discussed in the next chapter about [Junctions](junction.md), but for now, it's just a way to express a way down the hierarchy. As all MultiLocations are relative to the current location, Parachain B relative to Parachain A is one step up and one step down in the hierarchy. +Relay chain A completely encapsulates Parachain A and B (indicated by the arrows) and parachain A encapsulates an account `0x00...`. +So RelayA is higher in the hierarchy than ParaA and ParaB and can be described as the `parent` of these parachains. +The `parents: u8` in the MultiLocation struct describes the number of steps in the hierarchy we want to move up. +The `interior: Junctions` express the steps in the hierarchy we want to move down. +The `Junctions` type will be further discussed in the next chapter about [Junctions](junction.md), but for now, it's just a way to express a way down the hierarchy. +As all MultiLocations are relative to the current location, Parachain B relative to Parachain A is one step up and one step down in the hierarchy. + +To get a better understanding of this concept, we show some simple MultiLocations in the code example below. +The first two examples are relative to RelayA and the second set of examples is relative to ParaB. +In the `Location` comments, we expressed the locations in text. +The `..` express a step up in the hierarchical structure (the “parent” or the encapsulating consensus system). +The `..` are followed by some number of [Junctions](junction.md), all separated by `/`. +The `X1` and `X2` types explain the number of `Junction`s that we step down in the hierarchical structure (see [Junctions](junction.md) for an explanation). -To get a better understanding of this concept, we show some simple MultiLocations in the code example below. The first two examples are relative to RelayA and the second set of examples is relative to ParaB. In the `Location` comments, we expressed the locations in text. The `..` express a step up in the hierarchical structure (the “parent” or the encapsulating consensus system). The `..` are followed by some number of [Junctions](junction.md), all separated by `/`. ```rust,noplayground // From: RelayA // To: ParaB -// Location: /Parachain(2000) +// Location: Parachain(2000) MultiLocation {parents: 0, interior: X1(Parachain(2000))}; // To: Account in ParaA -// Location: /Parachain(1000)/AccountId32(0x00..) +// Location: Parachain(1000)/AccountId32(0x00..) MultiLocation { parents: 0, interior: X2( @@ -45,7 +68,7 @@ MultiLocation { // From: ParaB // To: RelayA -// Location: ../here +// Location: ../Here MultiLocation {parents: 1, interior: Here}; // To: Account in ParaA // Location: ../Parachain(1000)/AccountId32(0x00..) diff --git a/src/fundamentals/multilocation/example.md b/src/fundamentals/multilocation/example.md index 213add0..b3fcffc 100644 --- a/src/fundamentals/multilocation/example.md +++ b/src/fundamentals/multilocation/example.md @@ -1,5 +1,5 @@ # Example -In this example we show different `MultiLocation`s for the consensus hierarchy in the image below. +In this example we show different `MultiLocation`s for the system hierarchy in the image below. ![Example](./../images/MultiLocation_Example.png) ```rust,noplayground diff --git a/src/fundamentals/multilocation/junction.md b/src/fundamentals/multilocation/junction.md index c34e53a..af8dd05 100644 --- a/src/fundamentals/multilocation/junction.md +++ b/src/fundamentals/multilocation/junction.md @@ -1,5 +1,8 @@ # Junction(s) -In the section on [MultiLocations](README.md), we looked at the MultiLocation struct. We talked about the Multilocation being a way to describe moving from one place in the consensus hierarchy to another. The `parents` parameter expresses the number of steps up in the hierarchy. In this section, we dive further into the MultiLocation struct and explain how we can use the Junctions type to describe steps in the consensus hierarchy. +In the section on [MultiLocations](README.md), we looked at the MultiLocation struct. +We talked about the Multilocation being a way to describe moving from one place in the system hierarchy to another. +The `parents` parameter expresses the number of steps up in the hierarchy. +In this section, we dive further into the MultiLocation struct and explain how we can use the Junctions type to describe steps in the system hierarchy. Take a look at the MultiLocation struct again: ```rust,noplayground @@ -9,7 +12,11 @@ pub struct MultiLocation { } ``` -The consensus hierarchy consists of 1-to-n relations. Each place in the consensus hierarchy has one parent, so there is only one way up the hierarchy. That is why we can use a `u8` to describe the number of `parents` we want to move up. But moving down is a bit more difficult, as one consensus system can encapsulate multiple other consensus systems(e.g. a relaychain can have multiple parachains). So to describe the correct steps down the hierarchy, we use the `Junctions` [type](https://paritytech.github.io/polkadot/doc/xcm/v3/enum.Junctions.html). +The system hierarchy consists of 1-to-n relations. +Each place in the system hierarchy can only ever have one parent, so there is only one way up the hierarchy. +That is why we can use a `u8` to describe the number of `parents` we want to move up. +But moving down is a bit more difficult, as one consensus system can encapsulate multiple other consensus systems(e.g. a relay chain can have multiple parachains). +So to describe the correct steps down the hierarchy, we use the `Junctions` [type](https://paritytech.github.io/polkadot/doc/xcm/v3/enum.Junctions.html). ## Junctions Type @@ -24,10 +31,14 @@ pub enum Junctions { X8(Junction, Junction, Junction, Junction, Junction, Junction, Junction, Junction), } ``` -The `Junctions` enum can represent zero to eight steps down the hierarchy. When the `Here` junction is used, it means that we do not have to take steps down the hierarchy. We can for example describe the current location with `{parents: 0, interior: Here}` or the Parent location with `{parents: 1, interior: Here}`. If we want to take steps down the hierarchy, we express each step as a Junction. +The `Junctions` enum can represent zero to eight steps down the hierarchy. +When the `Here` variant is used, it means that we do not have to take steps down the hierarchy. +We can for example describe the current location with `{parents: 0, interior: Here}` or the Parent location with `{parents: 1, interior: Here}`. +If we want to take steps down the hierarchy, we express each step as a Junction. ## Junction Type -A [Junction](https://paritytech.github.io/polkadot/doc/xcm/v3/enum.Junction.html) describes a step down in the Hierarchy. The `Junction`s are defined as follows: +A [Junction](https://paritytech.github.io/polkadot/doc/xcm/v3/enum.Junction.html) describes a step down in the Hierarchy. +The `Junction`s are defined as follows: ```rust,noplayground pub enum Junction { @@ -60,32 +71,47 @@ pub enum Junction { ``` #### Parachain -The `Parachain` junction is used to describe a parachain from the point of a relaychain. Each parachain has an Id, e.g. statemine in the Kusama network has Id 1000. +The `Parachain` junction is used to describe a parachain from the point of a relay chain. +Each parachain has an Id, e.g. Statemine in the Kusama network has Id 1000. #### PalletInstance -The `PalletInstance` junction is used to describe a pallet in one of the parachains or relaychain. Each pallet has an Id that can be used for the `PalletInstance` +The `PalletInstance` junction is used to describe a pallet in one of the parachains or relay chain. +Each pallet has an Id that can be used for the `PalletInstance`. +This junction is mainly used for FRAME based systems. #### AccountId32 and AccountKey20 -Each of these junctions can be used to describe an account located in the current consensus system. The `AccountId32` is used to describe substrate-based accounts, while the `AccountKey20` is mainly used to describe Ethereum or Bitcoin-based accounts or smart contracts. Both junctions express an account based on the context they are used in. If the current location is the Relaychain, then the junctions describe an account in the relaychain. The same is true for each parachain location. +Each of these junctions can be used to describe an account located in the current consensus system. +The `AccountId32` is used to describe substrate-based accounts, while the `AccountKey20` is mainly used to describe Ethereum or Bitcoin-based accounts or smart contracts. +Both junctions express an account based on the context they are used in. +If the current location is the relay chain, then the junctions describe an account in the relay chain. +The same is true for each parachain location. #### GeneralIndex and GeneralKey -Non-descript indices and keys within the current context location. The usage will vary widely owing to its generality. An example use case for the `GeneralIndex` is to describe an Asset within an Assets Parachain. +Non-descript indices and keys within the current context location. +The usage will vary widely owing to its generality. +An example use case for the `GeneralIndex` is to describe an Asset within an Assets Parachain. NOTE: Try to avoid using this and instead use a more specific item. #### AccountIndex64 -The `AccountIndex64` can be used to describe an account index for the Indices Pallet. +The `AccountIndex64` can be used to describe an account index. +This may be used when the context is a Frame-based chain and includes e.g. an indices pallet. #### OnlyChild -The `OnlyChild` junction can be used to describe the child of a location if there exists a 1-to-1 relation between the parent and child in the consensus hierarchy. The `OnlyChild` junction is currently not used except as a fallback when deriving context. +The `OnlyChild` junction can be used to describe the child of a location if there exists a 1-to-1 relation between the parent and child in the system hierarchy. +The `OnlyChild` junction is currently not used except as a fallback when deriving context. #### Plurality The `Plurality` junction is used to describe a pluralistic body existing within the current consensus location. 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. See the [BodyId documentation](https://paritytech.github.io/polkadot/doc/xcm/v3/enum.BodyId.html) for a better understanding of the bodies that the `Plurality` junction can represent. +things such as multisigs also. +See the [BodyId documentation](https://paritytech.github.io/polkadot/doc/xcm/v3/enum.BodyId.html) for a better understanding of the bodies that the `Plurality` junction can represent. #### GlobalConsensus -A global network (e.g. Polkadot or Kusama) is capable of externalizing its own consensus. This is not generally meaningful outside of the universal level. An example would be describing the Kusama relaychain from the perspective of the Polkadot relaychain as `{parents: 1, interior: GlobalConsensus(Kusama)}`. An example use case could be routing XCM messages between global consensus networks using bridges. +A global network (e.g. Polkadot or Kusama) is capable of externalizing its own consensus. +This is not generally meaningful outside of the universal level. +An example would be describing the Kusama relay chain from the perspective of the Polkadot relay chain as `{parents: 1, interior: GlobalConsensus(Kusama)}`. +An example use case could be routing XCM messages between global consensus networks using bridges. ## Multiple ways to create a MultiLocation ```rust,noplayground @@ -94,79 +120,15 @@ MultiLocation {parents: 0, interior: Here}; MultiLocation::new(0, Here); MultiLocation::here(); MultiLocation::default(); -Here.into(); +let _: MultiLocation = Here.into(); // Parent Location MultiLocation {parents: 1, interior: Here}; MultiLocation::parent(); -Parent.into(); +let _: MultiLocation = Parent.into(); // Conversion MultiLocation { parents: 2, interior: X2(Parachain(1), GeneralIndex(1))}; -(Parent, Parent, Parachain(1), GeneralIndex(1)).into(); -```# MultiLocation -The [MultiLocation](https://paritytech.github.io/polkadot/doc/xcm/v3/struct.MultiLocation.html) type identifies any single location that exists within the world of consensus. It is quite an abstract idea and can represent all manner of things that exist within consensus, from a scalable multi-shard blockchain such as Polkadot down to a lowly ERC-20 asset account on a parachain. MultiLocations are used to identify places to send XCM messages, places that can receive assets, and then can even help describe the type of an asset itself, as we will see in [MultiAsset](../multiasset.md). - -### Location is relative -MultiLocation always expresses a location relative to the current location. You can think of it a bit like a file system path but where there is no way of directly expressing the “root” of the file system tree. This is for a simple reason: In the world of Polkadot, blockchains can be merged into, and split from other blockchains. A blockchain can begin life very much alone, and eventually be elevated to become a parachain within a larger consensus. If it did that, then the meaning of “root” would change overnight and this could spell chaos for XCM messages and anything else using MultiLocation. To keep things simple, we exclude this possibility altogether. - -### Hierarchical structure -Locations in XCM are hierarchical; some places in consensus are wholly encapsulated within other places in consensus. A parachain of Polkadot exists wholly within the overall Polkadot consensus and we call it an interior location. Or a pallet exists wholly within a parachain or relaychain. Putting it more strictly, we can say that whenever there is a consensus system any change in which implies a change in another consensus system, then the former system is interior to the latter. - -### So what is a MultiLocation: Simple example -A quick summary of the previous points: -- A MultiLocation identifies any single location that exists within the world of consensus. -- A MultiLocation is always relative to the current location. -- MultiLocations in XCM are hierarchical. - -Now take a look at the MultiLocation struct: -```rust,noplayground -pub struct MultiLocation { - pub parents: u8, - pub interior: Junctions, -} +let _: MultiLocation = (Parent, Parent, Parachain(1), GeneralIndex(1)).into(); ``` -As we have already discussed, locations in XCM are hierarchical. The following image shows an example of such a Hierarchy. - -![Simple Example](./../images/MultiLocation_simple_example.png) - -Relaychain A completely encapsulates Parachain A and B (indicated by the arrows) and parachain A encapsulates an account `0x00...`. So RelayA is higher in the hierarchy than ParaA and ParaB and can be described as the `parent` of these parachains. So the `parents: u8` in the MultiLocation struct describes the number of steps in the hierarchy we want to move up. The `interior: Junctions` express the steps in the hierarchy we want to move down. The `Junctions` type will be further discussed in the next chapter about [Junctions](junction.md), but for now, it's just a way to express a way down the hierarchy. As all MultiLocations are relative to the current location, Parachain B relative to Parachain A is one step up and one step down in the hierarchy. - -To get a better understanding of this concept, we show some simple MultiLocations in the code example below. The first two examples are relative to RelayA and the second set of examples is relative to ParaB. In the `Location` comments, we expressed the locations in text. The `..` express a step up in the hierarchical structure (the “parent” or the encapsulating consensus system). The `..` are followed by some number of [Junctions](junction.md), all separated by `/`. - -```rust,noplayground -// From: RelayA -// To: ParaB -// Location: /Parachain(2000) -MultiLocation {parents: 0, interior: X1(Parachain(2000))}; -// To: Account in ParaA -// Location: /Parachain(1000)/AccountId32(0x00..) -MultiLocation { - parents: 0, - interior: X2( - Parachain(1000), - AccountId32{network: None, id: [0u8; 32]} - ) -}; - -// From: ParaB -// To: RelayA -// Location: ../here -MultiLocation {parents: 1, interior: Here}; -// To: Account in ParaA -// Location: ../Parachain(1000)/AccountId32(0x00..) -MultiLocation { - parents: 1, - interior: X2( - Parachain(1000), - AccountId32{network: None, id: [0u8; 32]} - ) -}; -``` - -## What's next: -- More information about [junctions](junction.md) -- More MultiLocation [examples](example.md) -- Expressing assets using Multilocations: [MultiAsset][../multiasset.md] - From 8dcf8146c9df68cfa82a13c3d55dce651ab57f74 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Thu, 20 Apr 2023 12:20:35 +0200 Subject: [PATCH 12/73] Update src/fundamentals/multilocation/README.md Co-authored-by: Keith Yeung --- src/fundamentals/multilocation/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fundamentals/multilocation/README.md b/src/fundamentals/multilocation/README.md index d82b311..25b11b5 100644 --- a/src/fundamentals/multilocation/README.md +++ b/src/fundamentals/multilocation/README.md @@ -1,6 +1,6 @@ # MultiLocation The [MultiLocation](https://paritytech.github.io/polkadot/doc/xcm/v3/struct.MultiLocation.html) type identifies any single location that exists within the world of consensus. -It can represent all manner of things that exist within consensus, from a scalable multi-shard blockchain such as Polkadot down to a lowly ERC-20 asset account on a parachain. +It can represent all manner of things that exist within consensus, from a scalable multi-shard blockchain such as Polkadot down to a ERC-20 asset account on a parachain. MultiLocations are used to identify places to send XCMs, places that can receive assets, and then can even help describe the type of an asset itself, as we will see in [MultiAsset](../multiasset.md). ### Location is relative From 5827a6bd1d359d0a3b28137eb1da11f25eba464a Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Thu, 20 Apr 2023 14:06:31 +0200 Subject: [PATCH 13/73] Update src/fundamentals/multilocation/README.md Co-authored-by: Keith Yeung --- src/fundamentals/multilocation/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fundamentals/multilocation/README.md b/src/fundamentals/multilocation/README.md index 25b11b5..d141763 100644 --- a/src/fundamentals/multilocation/README.md +++ b/src/fundamentals/multilocation/README.md @@ -48,7 +48,7 @@ The first two examples are relative to RelayA and the second set of examples is In the `Location` comments, we expressed the locations in text. The `..` express a step up in the hierarchical structure (the “parent” or the encapsulating consensus system). The `..` are followed by some number of [Junctions](junction.md), all separated by `/`. -The `X1` and `X2` types explain the number of `Junction`s that we step down in the hierarchical structure (see [Junctions](junction.md) for an explanation). +The `X1` and `X2` variants are expressing the number of `Junction`s that we step down in the hierarchical structure (see [Junctions](junction.md) for an explanation). ```rust,noplayground From 4acf636e7f4ef65b53d45c1fcc45593eece0e433 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Tue, 25 Apr 2023 17:58:01 +0200 Subject: [PATCH 14/73] executor config draft --- src/SUMMARY.md | 2 +- src/executor_config/README.md | 218 ++++++++++++++++++++++++ src/executor_config/images/executor.png | Bin 0 -> 190024 bytes 3 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 src/executor_config/README.md create mode 100644 src/executor_config/images/executor.png diff --git a/src/SUMMARY.md b/src/SUMMARY.md index c826b5c..56131b3 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -25,7 +25,7 @@ - [Channels]() - [When all else fails]() - [Misc]() -- [Config Deep Dive]() +- [Config Deep Dive](executor_config/README.md) - [Testing]() - [Separation of concerns]() - [Simulating message execution]() diff --git a/src/executor_config/README.md b/src/executor_config/README.md new file mode 100644 index 0000000..8686ec3 --- /dev/null +++ b/src/executor_config/README.md @@ -0,0 +1,218 @@ +# Executor Config +As previously mentioned, the xcm-executor is the first Cross-Consensus Virtual Machine(XCVM) implementation. +It handles the correct interpretation and execution of XCMs. +Each chain that implements the xcm-executor, can configure it for their use case. +In this chapter we will go over this configuration, explain each config item and give some examples of pre-defined solutions for these items. + + +## XCM Executor Configuration +Below we list the [Config](https://paritytech.github.io/polkadot/doc/xcm_executor/trait.Config.html) of the xcm-executor. +The Config is implemented with as a trait that expects multiple Types. +Each Type specifies the traits that the implementation must have implemented. +Some of these types will use a default implementation in most situations (e.g. +RuntimeCall). +Other types have a default implementation specified by the unit type `()`. +There are also types that are highly configurable, and in certain cases will have multiple implementations (e.g. +Barrier). +These implementations are then grouped using a tuple `(impl_1, impl_2, ..., impl_n)`. +The execution of the tuple type is consequtive. +For most of these types there are pre-defined solutions. +These solutions are listed in the xcm-builder [folder](https://github.com/paritytech/polkadot/tree/master/xcm/xcm-builder/src). + +We will now explain each type and go over some of the implementations of the type: + + +```rust, noplayground +/// The trait to parameterize the `XcmExecutor`. +pub trait Config { + type RuntimeCall: Parameter + Dispatchable + GetDispatchInfo; + type XcmSender: SendXcm; + type AssetTransactor: TransactAsset; + type OriginConverter: ConvertOrigin<::RuntimeOrigin>; + type IsReserve: ContainsPair; + type IsTeleporter: ContainsPair; + type UniversalLocation: Get; + type Barrier: ShouldExecute; + type Weigher: WeightBounds; + type Trader: WeightTrader; + type ResponseHandler: OnResponse; + type AssetTrap: DropAssets; + type AssetClaims: ClaimAssets; + type AssetLocker: AssetLock; + type AssetExchanger: AssetExchange; + type SubscriptionService: VersionChangeNotifier; + type PalletInstancesInfo: PalletsInfoAccess; + type MaxAssetsIntoHolding: Get; + type FeeManager: FeeManager; + type MessageExporter: ExportXcm; + type UniversalAliases: Contains<(MultiLocation, Junction)>; + type CallDispatcher: CallDispatcher; + type SafeCallFilter: Contains; +} +``` + +### RuntimeCall +The `RuntimeCall` type is equal to the RuntimeCall created in the `construct_runtime!` macro. + +### XcmSender +The XcmSender type implements the `SendXcm` trait, and defines how the xcm_executor can send XCMs (which transport layer it can use for the XCMs). +This type normally implements a tuple for one or more [transport layer(s)](Todo Transport Layer Link). +For example a parachain can implement the XcmSender as: +```rust,noplayground + ( + // Two routers - use UMP to communicate with the relay chain: + cumulus_primitives_utility::ParentAsUmp, + // ..and XCMP to communicate with the sibling chains. + XcmpQueue, +); +``` +If a parachain does not implement the XcmpQueue, it will not be able to send messages to other parachains. +This can be useful for controlling the destinations that an XCM can be send to. + + +### AssetTransactor +The `AssetTransactor` type implements the `TransactAsset` trait and defines how the xcm-executor can convert `MultiAsset`s from and to on chain assets and how to transfer these assets between accounts, or from and to the holding register. +As chains can support different types of currencies (native tokens), fungibles and non-fungibles, we can configure the AssetTransactor in different ways, depending on the chains implementation fo these types. +Three default implementations are provided in the xcm-builder, namely the `CurrencyAdapter`, `FungiblesAdapter` and `NonFungiblesAdapter`. + +### OriginConverter +The `OriginConverter` type implements the `ConvertOrigin` trait and defines how the xcm-executor can convert a `MultiLocation` into a `RuntimeOrigin`. +Most xcm-executors take multiple implementations in a tuple for this configuration as there are many different MLs we would like to convert. +Take for example the `SignedAccountId32AsNative` implementation, that can convert an `AccountId32` Junction to an signed RuntimeOrigin. +There are many pre-defined solutions in the xcm-builder (e.g. +converting parachains (siblings or children) and relay chain or root origins). + +```rust,noplayground +// ThisNetwork = Kusama; +SignedAccountId32AsNative; +``` + +### IsReserve +The `IsReserve` type must be set to specify which `` pair we trust to deposit reserve assets on our chain. +We can also use the unit type `()` to prohibit reserve asset transfers. +An example implementation is the `NativeAsset` struct, that accepts an asset iff it is a native asset. + +### IsTeleporter +The `IsTeleporter` type must be set to specify which `` pair we trust to teleport assets to our chain. +We can also use the unit type `()` to prohibit asset teleportations. +An example implementation is the `NativeAsset` struct, that accepts an asset iff it is a native asset. + +### UniversalLocation +The `UniversalLocation` type describes the location of the runtime implementing the xcm-executor in the consensus universe. +Below we give some examples of `UniversalLocation` implementations. + +```rust,noplayground +//Polkadot +X1(GlobalConsensus(NetworkId::Polkadot)) +//Kusama +X1(GlobalConsensus(NetworkId::Kusama)) +//Statemint +X2(GlobalConsensus(NetworkId::Polkadot), Parachain(1000)) +``` + +### Barrier +Before any XCMs are executed, they need to pass the `Barrier`. +The `Barrier` type implements the `ShouldExecute` trait and can be seen as the firewall of the xcm-executor. +Each time the xcm-executor receives an XCM, it check with the barrier if the XCM should be executed. +We can also define multiple barriers for our `Barrier` type by using a tuple. +During execution, each barrier is checks, and if one of them succeed, the XCM is executed. +Example of a `Barrier` implementations is `AllowTopLevelPaidExecutionFrom` that accepts the XCM if the `T` contains the origin of the XCM and the XCM contains the `BuyExecution` instruction. +To accept all XCMs that pay for execution we could set the barrier to `AllowTopLevelPaidExecutionFrom`. +There are multiple pre-defined barrier implementations in the xcm-builder. + +### Weigher +The `Weigher` is responsible for weighing full XCMs and individual instructions. +This weight is calculated before the XCM execution, and this calculated weight is checked against the weight_limit. +If the weight is more than weight_limit, the xcm will not be executed. +The weight is also passed to each `Barrier`, as certain barriers execute weight-based checks. +After the execution of the XCM, unused weight is refunded (if possible). +There are pre-defined `Weigher` solutions in the xcm-builder. +The most used is the `FixedWeightBounds`: +```rust,noplayground +// BaseXcmWeight is a const weight. +FixedWeightBounds; +``` +Note: [More information](Todo) about weight. + +### Trader +The `Trader` type is responsible for buying weight in the `BuyExecution` instruction using assets in the holding register and to refund unspend weight. +One of the first implementations of the `Trader` is defined in the xcm-builder, namely the `UsingComponents` trader. + +### ResponseHandler +The `ResponseHandler` type is responsible for handling the `QueryResponse` instructions. +A `ResponseHandler` implementation has to implement the `OnResponse` trait. +One of the implementations of the `ResponseHandler` is the `pallet-xcm`. +This will be the main implementation for most FRAME-based systems that implement the XCM-executor. +Another option is to use the unit type `()` if you do not want to support `QueryResponse`. + +### AssetTrap +The `AssetTrap` type is responsible for handling the funds left over in holding after the execution of the XCM. +The assets are stored in the AssetTrap and can be claimed using the ClaimAsset instruction. +One of the implementations of the `AssetTrap` type is the `pallet-xcm`. +Another option is to use the unit type `()` if you do not want to support asset trapping. +In this case, the assets that are left in holding are burned. + +### AssetClaims +The `AssetClaims` type is responsible for claiming trapped assets. +It is during execution of the `ClaimAsset` instruction. + One of the implementations of the `AssetClaims` type is the `pallet-xcm`. +Another option is to use the unit type `()` if you do not want to support asset claiming. + +### AssetLocker +The `AssetLocker` type is responsible with handling locking and unlocking assets. +One of the implementations of the `AssetLocker` type is the `pallet-xcm`. +Another option is to use the unit type `()` if you do not want to support asset locking. + +### AssetExchanger +The `AssetExchanger` type implements the `AssetExchange` trait and handles the exchange of assets for the ExchangeAsset instruction. +An option is to use the unit type `()` if you do not want to support asset exchanging. + +### SubscriptionService +The `SubscriptionService` type implements the `VersionChangeNotifier` trait and is used for the execution of the (Un)SubscribeVersion instructions. +When a chain receives the `SubscribeVersion` instruction, the `SubscriptionService` should send back a `QueryResponse` with the XCM version that the chain uses. +One of the implementations of the `SubscriptionService` is the `pallet-xcm`. +This will be the main implementation for most FRAME-based systems that implement the XCM-executor. + +### PalletInstancesInfo +The `PalletInstancesInfo` type implements the `PalletsInfoAccess` trait and is used in the `QueryPallet` and `ExpectPallet` instructions. +It supplies the information of all the pallets in the Runtime, and is therefore FRAME specific. +The unit type `()` can be used if you do not want to support pallet information. + +### MaxAssetsIntoHolding +The `MaxAssetsIntoHolding` type is used to set a limit on the number of assets in the Holding Register. +In the worse case, the Holding Register may contain up to twice as many assets as this limit. + +### FeeManager +The `FeeManager` type is used to manage what happens with the fees that need to be paid for certain XCM instructions. +A `FeeManager` implementation implements the `FeeManager` trait. +The FeeManager determines if fees should be paid (or if they are waived) and what to do with the paid fees. +The unit type `()` can be used if you want to waive every fee. + +### MessageExporter +The `MessageExporter` type implements the `ExportXcm` trait and is used to export a message to another consensus system. +The `MessageExporter` is different from the `XcmSender`. +The `MessageExporter` is able to spoof the origin of the message, meaning it can represent a different origin then the local (i.e. +the caller chain's) location. +The MessageExporter will mainly be used to send XCMs over bridges. +For a more in depth explanation, see the [ExportXcm trait](https://paritytech.github.io/polkadot/doc/xcm_executor/traits/trait.ExportXcm.html). +The unit type `()` can be used if you do not want to support XCM exporting. + +### UniversalAliases +The `UniversalAliases` type is used to list the origin locations and specific universal junctions to which they are allowed to elevate +themselves. +`UniversalAliases` is used in the `UniversalOrigin` instruction. +To not allow any alliasing of origins, `Nothing` can be used. + +### CallDispatcher +The `CallDispatcher` type is used by xcm-executor to dispatch calls that are passed in the `Transact` instruction with the given origin. +When no special call dispatcher is required, this can be set to the same type as `RuntimeCall`. +However, `CallDispatcher` can be used to customize call dispatch, such as adapting the origin based on the call or modifying the call. + +### SafeCallFilter +The `SafeCallFilter` type is used by the xcm-executor to whitelist calls that can be made in the `Transact` instruction. +This is a +temporary measure until proof size weights for XCM instructions are properly account for. +If you want to allow all calls in `Tansact`, use `Everything`. + +## What Next +Check out the [Kusama](https://github.com/paritytech/polkadot/blob/master/runtime/kusama/src/xcm_config.rs), [Statemine](https://github.com/paritytech/cumulus/blob/master/parachains/runtimes/assets/statemine/src/xcm_config.rs), or [Trappist](https://github.com/paritytech/trappist/blob/main/runtime/trappist/src/xcm_config.rs) for examples of how to implement the xcm-executor config. \ No newline at end of file diff --git a/src/executor_config/images/executor.png b/src/executor_config/images/executor.png new file mode 100644 index 0000000000000000000000000000000000000000..9cf4bd4eb26a2b57da1c07bcbd89bf326976ac24 GIT binary patch literal 190024 zcmeEt1y^Ouk|yp>;cf*K?(Xg`2X}XOmjVv%4u!i`;ZV4{7w+!v!|i_k?wy(LHGg2v zTDdY$W=2HX&K>bZ>|T

OCl_Ru(5ARt#I#5vRe2yc!fTSsrRHPqh2_X3)C@|mL zOdTOLzM!His@r^l)F>JvCH92L+?k7<+F4v*h+-g&dmV?UA3vyna~@G+MA zvcUqPIAoL02$=#(tQJQNckfgrAucxS*b9O<1cHno0GpXeVgd#E1S-0Jy0Nzd4kEYG zHZa@r{`Tf!iek^YZI9)Ite7Hk9`KZ znWss{j+v*^*RQNgh#;&p7&&8RQD`8<6~umCn3SNyB6`5u^ng(;VqV=E6)P)TtgRiG z6EY^@kdhmg?e_%AEqksVa<1i52-wMcW-g^Uj7gP$$$o61V zn$WD}f?Za9Som!)u!FS8?19|vipZV$_mNmFOb2On85Qfmh zB+({it`Z(u>V?UzS>myr{A}ZP5fYd9g`5$B0*Gos)PEy+lB$_qtDDMmY^8kFq1&b; zH=9WxI@_KGfW-`xwuYvMQh?05yWXpLd|2!w3Bx^l>MJ9FutS0T=Dz8Clmz(`=O1du zdoeo?5zq&o_cN6Y({%iKY8DxR7|c=-Tv`y(0@T+4sA?AMgRT<*eF+*!{6#cyF8~}( z=o?C4vH{%^gp&~e66}3|vLYx_;5P@bEHJq|3>}cvZdV7qRp^d9B7{H<0|XxkbQFj! zVmQ&zBw|-dXyzbVVyImRPeDIL#xIcl!mx25Xd>46bc+0{Fv-Hu`BsNmhqNxBt#EgO zvH74DU%a4wLzNhDio45e>22Ux48ESihxd$bLU0mHggI{#IKb+_5{2V!!C$&Q@~9J zoLstG$XwMrF{>mkQBJ|CU(}+G#rG7O6p>XJl)^1EGYF%_WPa?;%1^ag8ecnIt6y(j zSL}}NP7XiNubUD{U`K>lGU%GY#H(BHx zhw6aZylRJPX!(mOLwTpVilVcUj53X~wF;hENjXp1&3x3{WJOou=xqA!7cbb3ME>IZ zA6r!<3!J6XRZRJ1B{;<`fCjyrGEAkYe2f~k!ufo*;;QPinhy0g^_K`Fq44~~(guwZ znS;dY9J`Qn^|h~SsC;64DSE&3s`PUC#Wnx}x?Rd2IiL#xhH*Bv8=JpKY%FZkEHuqL zMq$|NnYQuHIF11!0Fx%k+ES-!5#xHMSvdsuojlrbRp^<>H54}wgKo(KhrsST@@P*J#m^dO|$$hYeZB#1c z(Zb&B=hWb?eXwvI3ne?cLQ@nb?32Pc9gyeIN{niJ^MLOv4*#EcG5BWLL!sg zZ>ZKV;A7*v@Z|EG;49)Iyo>cFPD5y!cX&|_54ObTEvf$i<{}CPE09@=~7%)R3&|aiVN!;`gU}G3Qs ziHw+^xYit11e$|7?hbs7{C2*BP*sRrcwy{bwv~TGSCiV0_RSi`Lo)?zB9>+n3Njj) z%rBv9h^(B8oWxp?o49SN9#P-OJ_gTiQ&#S6UvdV=+fwXP^s@w7vK!w1DC`R)zq0-$ z^OJB7leJ;5y36@l4hAUzz4>g z>SQXacPqRthA~E!NR2xk;{o$6zorW<4q%>%x`(=(Ka!`)I^; zaulmAP4}RNQ_r%)%Kz?^ZQy6|3Hu_Rz2=5Rv!!oC{z`G%Rmxe_{)$_Zwio6@j@BT+Q3yyxq^)Iws!dbDp^XL|{=LR4-UZ^i5Q zX4A7@=DRcaIXFj7IPV3)E<)!s-_FBueil{|R)6nt?>reMSye$!L6bhcFW0T~qt&+S zIZ?fTQrEP<^S(l=fIUzw2C)O9BipO#tz+iJn5dH91qdB2@RIiIxUI5oUNotukFDR@ zDegm%qsbMKN&94WS+&=8@z7Z|S4K^u-Is#{srbF;>I$Tt2MUA&4&>Mt` z?>jyuJ8bZexaFre2G@^SFsX&EWq;5Q4RCg-nGnNIwN>3zQ_4(E4&>Ws83qJ2+!6%h zvjqB?a6c0W2zWvm2=r%;_L)TU!2YWhLO&1uzsjIJe{~d65s{Mm%vFq?Oik^aE$m%{ zl!x3uSuI+sYPx93$#NUp+tM4F*c+MBd)PYsMFPU>!TnjZHFYs0_OP|FbLRHoBmIva z+@IyYsu@U$|D%hGH6N*_oFcJ^y^|?1J3S*kBPl-|F)=Z(lZhF(vZ(mK$Upz_ky^O8 zIB+vCxVyX4yR*>SJDD>uadB}mFfubRGt+(cpmX-Lb20RwvvVfvMt(f3+|$(K9mqoA)Ov?_agt zik2RxHULpe+t20sl)=x+#>o30{r|6)za{>IQuA+0CMG7Xf0F*A>Hj2Eb2fDnvA6w{ z>B9f_*8Ge3pN;<_i<}Z|6J$)sQp}MemGu+e{UK;oL&y7FbIesh?J<1st4$) zFLVIr{QP~O=~0KqkwFCFX(Tb`DV;dFoRQo#`7a-TV6jg`hS_jxTFz1eHU{f`OeCTh z3cOgMWc+$+XKmZ*2k*f|cDB3Q`mgA)^wT%|Ra*r3w(X1f4U(g5p2NtjDcJd&GhV7i`A;{~ks}z5g%aHc; zY=1DxDz5kDFnP!Cl);scz%BXTj~H7-K!$SOM@ljmUvquVsgrTL(<_aII!ty;rQyjK zHk{(bVVdL~6q4PmVj$xnvK*5<6%9kh7j@Lp^J6$rmd-hhnG}Ves8q{pq(uI&b*JB! zDIj47uy9S5U>a}aK+bqGnUXBQwSA-RPe=D*SQn8#Zjz+-auKa_I z_kW(~ynJH80&xFax;I&NVSV`*P|VgFJ@30jyS279h%tRe zeQ6o#A|Mn6C$82HC)7;64sWRoy^i_EIpRHAQP~Te9T`T4Ina9cv~sdz8Z5f>*NgY& zfxdK*XiF45*CbpI=_Hz$fge9epSqF#h_l{$^b@RC>Z(@#FWzSIjl&B!p^iBGdLTTH zo>Cj>8~9HUQq#!!yzb-L&Rb=;-OusiEq!9`?$LLRioRUIQ&*RwwJ(^}&U{tMY0**#*l^qhSEl5qiB^=Rhfj3}VP z%=KA30ah;TX#mnjGhgeM6-9w~bV5Nazt;VlyT0W{XNNK)xGX#IiGQ;eX-i6?>G?`f zm-w5iK^n}eOm-1+oJ^TN@K8?XN_j3aE{kC^)jHHH`#xC=ebUal#gOLAbcnc^v z)KJ>az|i+#iJ_@{5jBpy7@@a_Th|=gnIpE)G`=}=2$ui?U8_V^ei(?-$Jz`2y<^!@ zml0QAOm2nsEJ8#B+|sL1Z?AgYi7Y0O9RGJn4#LP^SG-pSE~+@M8PY3mwx*|Pb#fuSNuJg;(Ne$t+&E!~tF;6X*3#%bl)m?T@cf*Vi-* zG6(Y6yML@}5=D^tRCzR-0G7N3E)e5gdZbf=Jiv0}h)6_m;U)Qc6sMYa+IU=pT8*Gb z3hS*Op~^bIvSb&V37qF}C!hIEjfOTq|5WiNfxnk!Dgfm-&-&y$qCorJh@b3U3H?ut zEmFYk5$TggcbCaW@O|oq|HlhAS(vxE1B1}pQm{+Qv2IJf(UlbrXc7Ru(r2ju2zuN? z^y_GzjCcY2lJ`jX^G4htENg}%Ez!j4a8)x4CQf+rp2bUM6#FQ;0x!uV?+p;hYf`xC zG8@gEPj=*x3WRV**nPO=yJ%Dnh%U6oXr@_F`+Blaotpc4OX;-n_Aoq=tphC)k{-zQ z`AW5rgeheX`6Xj&alPB5449mGns_s*uGDNHWsBzfTR#x&6(Y9uQN$3CM(79}O%cd? z&-XbfzYwh1BqZ5Wj-*&OUHuR$Fj1msO?k8Y4x^a5D~pp&hYm9p7mbEkhfqG^oIFaM z+6g9`oRT~;SEjOeTv3BL(OyBPArQ?lpY+R;#T}o4ZW)>HGVXOdwJQYFEcK7xx8h7S z7IYaqm^pSZ53b$w>xfLA`De7m*-#GqW!l6IFCK;zH)Gug%3Plx3-4eP46NpVKiG)K zfjUr%Pkn)Z8a-#tTAs`~M%%C*ra^iGxe!_W25fu5hdC#Z1H-C#4p>BFGQ|ya#*ho3 z2s48Ywy|T|wDXWP>vvh6repm>&Jb%kzT~(%Nz$`A0cWWs$XHvc6j==lbmhF=Guqz= zPPYC`;Mdhzg#9KI3mCO|Jeg}rY_wTc{?lkpSEbdIuwbjb(<%2@puc#od)M5;H{w8N zQe7kVF0}FRxeCX641azVWK;LD836-{C&Gz*O7%IV^A*!s{;e_viiZR&f+kd__Z7wj z5T&xM-Dj>iSdCpXvP0{zy{W8GjCI=`Lj>jlMLL+K;0rt@%vDTiZY)}#9VU&7;o&o7 z+V0PKm;#B294b-Xe%j7Lx#Au(o4=8N8^06!;q@Eb7W$D+zg{Pmq~Ba>9wOY^W->J^ zO#xTYRQeDvxeLk>!1eL?A~2F4j<3ZKvt6Xggo$3{v~4^YYhm*hxU>EC$`-G+=IzDq z9M3kP`xU5s`JK{6a5CMbaJv6O#bvLli%vt=GdbtJ?`QdHStgz=&p|bCl-1FOd2cI! zgd!$Z`$6L&l4`w0<*S?*Tgz6Vuo5a|sfWsXHffvN*$iUi_r$+W>c2G5(hV5V)X=$a zB~uuj;QRU? {py~wrQ_We^sWmrsn?U7@O0u@z4H3cV0IY4d>@Qw*pF)sd^VHiAr zoU1A!2Op-QaR!*f*^(yS%irLEN}9R>iYb{4bswyWe$K!dQ{pg99!iO9^z(PM9&VV+VDo7HMrw&5<#bB}V) zZ@jlO@s?c*2Luhk<{ zG$iBPVJr5K57sNb7M7iY`OTO#v5KtG+8+ZMrkT~>K_My4g%t*3i?5#A zZq8bUbx??|(l`PUnP3lVbFSf0Zwy;n$Z*kR!thl$AfFNz=PNXf6WjaMehj}GjU-V! zoz@kUC#|FXeeL`v6b`Ubt}id-Hn1LpJ7244V%E@7E|*E-uv;p9)*Pz)#O?>`2u@m7JXIncNK_yuILJe6rx zY}$j`AB<~QL7ph;H}uYN1Yff|lIOM}x|y(FS&h+%Z0;c5=_h%liV z2RUTntoh}IrGsLP{N4?9g&RfKZthcjlLLZy`=kDza~OD7_$yN_6#miC60F8ZQyc!v zJ2BDmib)>CT?7#?oBnuW(O6u_Yd6i+3=@I_9XQ?fML~kNa*H>D4=3C8mt)#kRU%5R zA+~;x1fQ3VFsa^Xf;a&vC47l#ORgZO2eu#Q)we@<*CQ>})J2=IGRG$1MEEb4M3!6I z_<`ke2RB!Xl6t$ww@cGKRnWZq){9li3J3n>EQb+zCn`hMdR22z9rAbh|uVr*_eXj%O>8 z^`UNALHte*mdWW;1I)sRGHMxHmF<7hOaAi@Rnet zDk{Gu6GK!E!;?YQ+f2#C6zb<~~ zaPZ8-pwSP*Z7b!ZRB1_{ENISvdBSC|&mwKG8JJtlV1ZanGY}0%j{D21!=(>{JFbPk zxm(1Mh>qp%3E^x1vN+nF(F_(nrA;eIq?DTop##^(4M|nN!QG76CaCvaVK-3zk&VGK z6qC&@ebXaMZ4p#}V986O0?-*!h-2V6IOF*xQl0%rsR5~u#ax$45WEDRPKz~Fy-Lfv z!?2Upp*a;UCVb+z0Km{EXm2J^qO*`UvzeQ_+L)O7deEOWrx}aN$Jn!0R#TweG3@E_ z`p&PP$8Csx^C$ROKx3MJ6eeAv%3zF^9dHuMJ&WCPKBT}fY6fMrKMKp5FN_W@noZ!? z#ys}@0hKR^pOtD)qQ?mD--fy%0qLe|f(;-BhfloveSIFc3P5seP)e~%79%mk|FC*< z{eJu4y3hEAPVidxan&~mU@;%5E08!DmMBMs8o~6;=a=aC^@V#E=}>|vD*q-qWwS+f z))`-LEl1;cr(C1SstDZV%>l!t`WZ)Jl&NFvMm#&_N!6$EmZVH$AlNCaom$WYz$PWq zRE#7|kay}<52c5B&!+JHY~_Trd-%+Pkyp2_75nIe8)2jf zg{YN_ex=p5BE=eZKyJa{1h`h}YyYxSTINI|t-?$Kcg@)$V1w&_luMOJegoIr zK5AKwq)cZUG=V&R>k}M&*mVBWXzBDq&@}pMd}Vw18=t*M?&biZC3ATaDMzfAy^o>! z0Rc_^1#1{G5q&Teazf%@bYiC8Eg7J}DLOXAN327u-6F!~k1Dxto9irRC1+*ED~I!f zZ2HX={?qL&WWCK1Gs0YytDtUs$5Q`qrG-yxPGzcHkOqtAhu^2!+KVipgT#Mma5UW@ z@ke|Z0e}hsZHCyU%lh*pIx2dM15LjGwMpzIgHwCF$CZ3(C{zY1zT478!pZ_`>zLez(GKp8{++nr-<2h=r5yXxWzim3+3fZ)}1I& z4|ckw`?Eh%spq;TyWiuP&kG%Aif!>8p|f?`RV1yzrTh%ccn}oqi9b>)J83J_s}s)p zR|3J!w^`@!;&ofy;uGb0rQ~^!M18wU@#CGokL;M5;M7)+rCr7Qv^wNZjBJiZ4Zz{L z&4J-hZSGIfKBQ1hA`$RNPOkIqr4_`+_agDg{>dx2!*pjW%s)*4g0DJy4~*eFlNvxD z9ScL6`#e3I%}U%ayXz-Mk?baa>@%b>jHWVZ$nf)6=B~T%0(oo>8ILx)x)$=yEQ~7_ ztNY`vV@KOAWRvM0;Y4=`2uv-*os3p*I$d(`J$?tqBk2^(EfIaxPo;i&^t*2{*X@u~ znLuH)AWsGm7w6JzQqic>rtUINX zXuk}4JyP&@g8J^l4KYyus`+^8JUPrX2vqhBH&Ddd8vOZmb0Bi@qPcGX{^4r9SoYpw zd=lfp#R**t9QA*d6zzq-JI?9w8c~uUC>mV+RJJH|W<>dPV#Q<17No&lN(UKqTUm`g z5pZ}#p^yhgK#pT1cL?r&wQM@0@@q{?&J;)};P9C>9{<31uhedm=YRM3H5SGTFQ7{$ zCYD*t4MIdoLK%&+uB1R7%PQDt6D{OAy6|lfRf3R2Bpg947&vg{Nyd+THB6(ABkx11 z^hlD>ei@PuXfPjrzB?)OSdm|jV1gf{d0iR+z`X`6=Zi=j&DO*M`Rpovw&3-rVoSzC z(h_5RvRNb}Ds}3Fb3Cr4JTE#dxo^rmwCfgU}NA~jg=zTC9{?T6#b!YmrU|Zi%s430(zG(wYa>NGm5^(=a?Frgvc6mfuBi+dh zMoOU9Z8ee|ye2jPwSzUPfA4ymSm8DdU`B=4b8nzkBCNz?B`1h+gg&8&7S;N2*yFqy z=h&D*zDvK{;gkXh;%+m${!ul!524j-)^tlgpP8qq45uxb$mSXCd^x4me>>K2os=#* zISK1B8=+0Dg~0zY{2Btgt*Cg;ALPuAe)3VORB$WuD95q3YgS{LR}{utHA@?efLf4# z;GlmI&G07(D8_d>n??T zP9f(VN~?VpYd5@2alLS2UzY57n`XQxqtC4mg@{{N-(ndygz!Pbb-BfZ<_4B!c+JE; zeQ=4Ec|FlqCe!ERRj7oe!%@AF*O0#Mf8pEmaPDm);8JOx?V57hCBU;JKm4R_)aQA* z*Uf--A?qn|-u2)IAY2xu%Lq|FpRZ6K9b^$$ps-xjb!y-0S6Y-tHxPw)PEUEGbQ(N!KZK{s z5kWBU4>1Hv8x#s6*dxOxa>JFYW0%L+*Jf zfZVOz#ZsAz1oxE+YoQ~VMtvlysbr{GWf1nE;8R!1-XVH=^4jz$eb-+oNgmZ08H+XB z7pMJZOb&U{8w3eI_K4QqXNUMN&@%8jG644rX{+>C{xa{6c$v3QeI8f$W`c<37bUZhQsWfn1G9+Cxho~S za1|{y4cZGN2%?2vxvV%X0s(KYkNc7kjjYsA6KOa?!P7TsKMTY|BMqW2K796iYKDFX zf?qC)np02qNSGY^%$9Us%s?((86SV5J}{}eue+223q@Gu!`=0*thc(v0!Bu8wbOib zI?ak#7Aw_NK6^C!3jpB`uRG;2Hl7)2Di*K05KfT&nH;vqp@;XEi%}K@0kWI@ac81} z^ibx3Ox18$%zD2yC^pQFr{_$sM+ihPV+CxQ-f+Z4W*NEo_Xr}AvdJ1iow%OQ6(r8b zB}8`B56{5_+ds{KL6o@_1%VTnrv;8K$O4s$TBJVL^^MhgJWt=5lU898D-_`rhgT0c z&>fE*&(VFlsjJMPA7W7^qyyMGs1Db|JG-pF3n(P)(x)Q?B*loY=DJ%K8eOj#X7>eI zy!XH_(J&wSZ_lYtL;2x7E0C8XYOu00dIhfuE2S&$7ruoW7kU;F&U+ikuiC|39J+|c z)12)a3nrO1nr%v$V{fV&B|dn@Av>`GdgghL02ZVukC6=j;(VYnOi+)%4%7h6OL#Cc zza}DMBp|GkOC8?)QQ~SW?rUn^v%OE?7aOYlA|$~XGl>&5l9Z?EU}$E7+4rixvTw zUS;xkI~7`sh1TGO_M9@fjcxlNndE<~WGT-Btpg3q{(8QAIn*1OOv5UVJt3|cIwBmk zMT4%N%t12Qtit)bQ`uCgOK})ggZ|r`Wh{(tmtRNi&C_9UY@;)o{V^}myH!?WT{=c!FaSEr+TG z2bGTX=7dVYCxY!t!?3;q0+9-X=U^11g4N68s3UQ6I0JH@&)Xia{VI>O%bNm9HCobw zV#-TqErVp!+=FH*ustjvd9>1+oM_UT>UUvYSTzUd~In>yN#aw92UO%3ue~vO4@u80@;69fNOX zd9&%$<3u}hJdbh4+&)z8UheD}6AKjuR0ntH`%(K}L1G;JBb~2%M=Q!;d97NA;RZq4 zYK1q;D09P5&&9Z2vKscTN9lX5K#m`2nsx=w(Uyc_IUh{I~!C?k||2$ptqfWht zWu-;+xEwGy>c&9O;YwI|L>pQtkU%CE5oXXpydtO|WE1_#J`AzR1Wy3m9@&%Z@NOIb zHDg*eW$ds&%Sc#d1o`M_m5B@Mvqv#>2lL^Kn+pSf^n7X zt9kgJF(}twM2?};ShF_fDgzEWoR96*kg`;t2nVwSyEySrcpX35|C{xf6(5tR{RuCq z!FsYTbixechc)*uw@TKD7T)w4mOhWM2r1>31f-{%ySPTFaHl$33vRDs$3(qc@4R&6{$9VeGF23ubkV?--ljUkc`{;1qW>E#Y)6=q{@ZkRV>DKRG z4rQT{aiF^Lcy1g$?vNf{pBph|9RV6D*H@0GR)1r(cX)w^Z3J1@zSS45hY(79Lr3!T zPTI7llY1Rq&fzOWO0&Y@GqKX-LT|wW-28+7ssh`7SLUKy^@$q#zG{9z94Z|`(G2;Y zh9(EMGCcNW?xv5^X~H|b^BW!Fss0A!J036|h}2+M^QcdOkv>r62{UH=Y7TQeqgVMZ zEM{S=jt2yqt=GldD+o|v^JUOG^^Xz%Irp zlggBPzDp3}?aaj89EM2j`HlVladS5_xL=>LNZACW{vK*RqjJhT$Fh*P zv2o{}etAj8jcKd(9j>pPH+Ne0i0Cr^diyF~f@q$?;S*z6Ep)l9F_c&?Jrj`}Pq+*5 zYj<)IRZ>!V#s?>aMmVf#aGQ~+)cS58q$Z;~Sk@Q_so#_>wle;>sm1QMH+iVjrZm;- zAp{AV>)bM&KU3S&!Lz5Ud%q3UjS=EJo)Eny!SODcsvUy(D@gV<1_Mry>k?BY?R|O{ zYz-^N8`PS~{3w9`Pq*W9o))T73l_Z=mB7pLWJ2oW?l+eZycD**X_~jEtnqwcg!>3W zDSiUA9jM>QLS^D`8cBZUC6qXw*=S4@HVvLqM*HN$!5+*^oew22mzc=sR!9)xLLB?c zEwo7By-9{UtFc0#uFQz$2Tk|^7++UP(xtVcYK-c2gu-qn!SbgwMYCVFvMDt7I2MYw5@OeQ9L)yAi5sb)grk7(SA)~w|8Im z>%Iw_W=JbNSg8V6Y9>?QX#V?UxInY1P+o;NFaV4J4*DqwUh0Nq0{N|c8_ccAZoT|j zAk}6X`-dkJ@aYmvH=YLjJ))q!1p|Z$(GzjIISrWA%K*e*-DT$gUI^%PY4Ou3)))%s zpCbDNtj%$vi3A9zw}C8RrjVOCVK+|xiLXA*3~nFzEoeLo!=H`=Y?ds{lT%W@FJx#X z+)Y^oL@^D_DBIFt*TTzgse%J}dTP?!d%GA;Oydi3+ItK7>*agr(L~8J^MT>N7_K4G zJ)fB#T;yC(M~5OPvkPDL$OkII%*msy6w4|KX&>@_9Oe-be@naEXtY#Or?;Tp)T3QT zJE;Mn7e}GkGETn`^Ye&ZixIwcxfCkD^3nOr!2gDH6JJD;GqIXRpQ z|HuHZ4UT@T*!9X1o&E$n;1ThS=(l{VkAAc+NkaiN-Hn8hSgOr4(K!?-8H1U16(;#& zSUh`S=n45l$})g zb2r>Ip`F~7OzxDjDLEFYT}U#y8hcqcNa!px z1rO)?6^&(IAgt^4n&x`bpU~w%&MR12lOZ`jL|}LD11$)>1FXg3R_FD|2;btSY{A*KM2eXb zt&B?{hqeDrg~;bpsO8|?``hl7*nSQE!?s^vK3VPXHE3BwsMYuENI6og?*_W@R|MZm z6vz9`BJeLZm8*gwEKVERi85x-z0|DxQ@)2@-tN zSJVt3o6C?HoFJ%Wc|th~l53K522V+~q5V@@?;wp=LO*kv7tJWFW>1Z0;fUpu)9g$x zFLE8@=}0{48bFg$E!iI*52iz-_uWoh;>4}0?#0X9HYBrF0G`8femsr~kEHbW&k(h^ zVDCZMB6(L5jGA}e>v+I&R;&Q1{20$8TE1zRS9d{epvs}cXzJJ44Hrm^hX zlaT>LJW~qxTHWVm<$jLVV4pO8r(P#OgB9YvA|PaZ>3(rwf`KjnHHj<1mAq_Q)=!1U z7+QI$u9*BgpGT|Yb|>N10EIh27ek^ZGyikF@9Wjzr~Tw3rr*5F)#Q~atvJiL5kNE| z;MemwC;2Y*8&Kp6iQ>(bJ2~peWdpcL}#pm?j zQCAjuFpnyRrSN6Fh}ch=582!z(sl>{f56jWv=1Z%j3Fluk9>D3X>PZhbDLdbSZOd{T-kxYIP z-V47fh_L|=FW!uQ&Y0MZ=-W|;v0Z6O*5mWq0!e(IDVD=~D{cMsw9;Y#sI2Q4;Gth# z$_u!D(ohfyxIJEqoZbyFVQd=b0Q+ifIr%e)3Pz<-QpQQ>V*TtQ$f7)d&~Y5-4kt=8 zoG)}R(#W`mpWiQ3ki&c9>74Oh_ys z<6-?fhDjQ|pWF-O6%?lCSa(Q6BUd>I>Q%G;#8f`#oM_Ps;D|@eFoMhG8fu+X1;mEURBF$U$9Tm*Q5ecyy9FjXSoRT8lSHP>T_r01wQw@Q$ z_BEX|`M%Kozqb%1r{ilKQPxE2;BV7*#7EbAA5!cXk)~ioBwDM{2jiWW?z~ain_3^% zy3D$E;XAAV)&(F`0h2^C84&K z4(*NyvEXR3SJE0ICPn~_7WuEJ3+_2CNrzwn$yCZa{D^{=_8l3E=^nA`3GEA27d?JP zFADnKE%qP$PDd1}BYrQwLb3H|vhk)Kxq#7v1+~~TkVTW~1c;GK{QPJ{Fvq23yc4u! zmu0{HC#=~QBjBy?v^W(h2^h02AFxAW#5c@76e&N&&NTnx0w51Sjz31U!9mybSwfy)3tlkov| zM4g{nhh&TZZ8%fsQSv?|M>Pzqjm0Slktx@^s#x z0iiMcWXA`ew;KnSx=}Z<`SG10p+>twFWUBusTk;&WZJPaXyTVdz-E?&@7t%u>~Kl! z<8a>HZQ@W1bo3({F7DMVI#}6Vxa|ph|qtGCqY2Kt>(9Mu#C;dZ^-oQG8ZC9Od8j1YF9Y9sY?>KP zJhhq;$|4NLI9z5B3l@=~F~B)a2L7}gIyR2yqFtr)W$j|59HRn1|8N6lQb$;9l!b1_ z<&>Au`<01LD?{@ucOj+S8`N#0wq1+Z^X*ZA4dq)jOPJKY@}@6c*j> z>6=sA#q8hr5JpMI^62U3)96#g z5h+m}cThfxrTjB~EfV`^r$~y>tL=J!!|BNas_RDpW(9-P*EoXCNe8xiC3&}`j_2DM z<1cK{7w|sZo7%rBWOKalxwwvp3Rq(qjoX<14t((n7OZ|o-Z`?tDDf++7Swe|kGXi; zO;p%TZF*Nmgf7Qoj9G+jFT4%zoce)b)_{Ol>}3q7<$e0gxdUEzyJ?~0eScUcp0fWq zB}EV+FiG}SYTOh^SU=hMGGy=PoT=k{#&%5|vpT*T$^PXCJ8%GNhSYkFpkkX-e(Q+i zT~zIO8J`9<6Ae3@vtsY{W->MKGqmk{JXsNinX}5Gi!r#l4GHw^PVDP#z!;zR{S3ub z)7$Ycpv~13a?I!t=scT{2_DOt!jUv@=_EF)*hB?BK&SJ(nip>~JQN4aPFgSv3b#AI zFp|J4%v$Z^C!&!&PqP^E=^6M^bFILgpA=6h=L)gXwI_hr@jSxEmuXiN~OX)pd} z6^`!g!?)RdtcraQ!*kAw<9lkcB>|wsKN+?IZom;NfDWiWbG_%U`yQORTlpILZfXiL z!8*V$2MCzkIiC*J8E9cCV2P6ZU$u&@J3D^9%Dabq3U(t$YXZj3SYZeAEA_kH&axjp z-p#Z+F^FLL=0RNd0|IfEiS_5H5J+q!EQ=ycTz|Hx{*yR&B znm1urE9s~9Z*)1|2ZC|Zt56FYzhh^9_=Fv4@I<pKn&-N@X+AAGSkN6X z&7Y=mYVIR|Z+u@mIs}5d42`#_QhV$hH0QqfzgtM5s?m!}QiW_jYU*50S2jg3F`8Y~B(ZOjC{ans-;EH1lfqIUp*f8f5n;P!~L$}Fp&7_Or zl+wOE5b0a6c868+34VkO`2~VhIe}>KVzP#xcB)!vpW@fbhrwtZvYL+j3|U`uD^i|L zm7NGf)`e1I^Xk{|x$E{V#}LzNl1}w98I>Arw{`Arpesw_dgC<)u=m+Dxdx03tyetOHh$&gc+3I-2pk^G3w){C&QRX%n( z19y;T_QpOK^e;{4nbNULzx59qhADNE5zS?P4~+>VMh`V#6%i-g<`1E$P?HYy_t2t> zX5jV%8=p^`@3bVl-`$mHENl$)c@=y)v-i~=w2=3mi-!!<@KXbI1s+$v`#!8__yz9# zJn&^k_6&sStv3*RKYYx8j-;tp434=GavF)=I4a4ePoz{xc_`_)2>zxYk38tA!@X-p z+d@U_KVhW&hW>!BaLg$YyvC{Wgg|BG3pW7w%GmLVv5u@HZpaV7S~hcCx+R&%+$RvH zX0*Is4&X7}Sce#ib5Y}&2TxPEz1=O2o&ty}w&}69kJ1-#MusYQ0cr6z(wiCj@QBnKBR10mMbX zsny^-9SMTZ)gO}``wXhXj=jOSWU!><06<^lccK07G0e@E~u}oh*tZ%sQ`~<7%MNep|`6?n>eD ziC$LLg6~wqyY;bZI_4%5@VFh$kZBbkjHM^0Q=t6l4s)_F6+3+>>AGNFzM}>Z8ioZ@ z=7gW|siuCQSobdtDmdFBR~$cmdI1LvHa9+Fh9nXj8#t@TsNj%#s0AFOw#TuK{L^3^VpJqUyLF1Y_<4{s#Gx!r?2&j6W>I*2c2$&y+NIH20!w1u^LK`i%wk6AV zwPzd{`Z?o*LOU*@*)+GNG#bK?3p20uz`d={NlCZb=a`VOWnX zga=zj14o_iE3iAjxnZ+m@Cx?Q$;U8$1a2o)=O(XX+o`fe?L z7A@&n~|;FWsNNUD9v)E?`6?nzqM*Pu*LaL|p!7wgAq=KBu!Ej8(RXfp*MtDW8!gNrR8Qi}9_OG(WF6%R++kY?n3C2{@)?I7E~~b`qLC6s!eb~sXlourSFm* zQzs}w|BJG>3X7|2wnl>m2=4Cg9^8UUaDuzLy9altgOfDw1PJcZIKkcB-R(5*-rxS- z|MFb*)BW^Xt7gr)rc{kFs(MILIDbRPKMTzn8aX8oS-gnE+)BS|J*k;^ntVp0WUlRD zR_DHr4guTHMUSv{M3r9k+Bz42uHtO0;EVQ`2@BA2SI4C-eT*jc@P# zRlNW4Pf614H;7wkqxPZvq790xPr~-M!|5CTx*|Q%YXhHiLUy#`(nYPax@>=f2|z|M z`{u^tgXYZ^+HrRS8lo7!`F+BNoo44ntzd*#+(8$cwdp6TNTvXK0<&JESPZ_(UjLBZ zcn0WI->a|X=sy^vYZ`WUndaM#o|wZ~hA264VK`I2F}CbeJZx!>Y!M<2yPsW*VzV$- zNsIr1z}K$xWbmwcU!_ka z;?ZT)oPm#C8jBo<<5fB-(NaYk0iV75s=Xo(W*AGn^v~ZvLx_G9C7wiGZ>w8m7UHW6kmBUT!^lAG?jmfs~ z+pD5;T7J0mrkc8c`r%?0O<)1GhVz%J|FR;39>W3;iGwQh9%0XdJ=!kuLkhNhdLz*0 z5q9MG@>sT#NNA&{ab$XrS`e^(OHD##suYrD=0m<+!K=9x9Nh@#%A^VS)tyJ(^C3{2 zpFf0cz;hGo{FUxvxqOb0OfM1h@k`~z{dS6_^_TP;KdW3gO^q`Pw!iJJRHgIb-of@y z+vgpsWF7Y^nhA56xfTZpLAn(3LtIj&A)eX@yrMXS{Vm*IXrZ47P1N}n<)jIO%NIk>I^ku;P=ppaX^krt&GyyNBCbEJ_+uZJDLig}L~>)-2zO%Wx0R^JEf zjM7&l{Sd{P5rq)r-|Z7{J(ueov4;5~0f(DbdP*Kej}xG+1P*FCk_VvSa7POpM_ zUOXoqv?u&{1Ch7B=6=@G&)`wMyZZUeMJ5IO-0nCYrQ^`1&h5N&%?~*m)gJ(zOFH>- zS0lB38n6t+%1|BLZ@m#%ENS`hd=rM_y}nxie8}mKF6&sllC|tl zmd6rN7yTx_=$p zwO6zGP{>-pro2(U0Q1{E^1LNa=7oe$8XQJS`i@kX`Mh_n{~U@eDF~{?_}WQzF5I!R$L?`UqcCcgKj26f zgAgzY2NH<^8N~f&v%82h&W0zRyyiyZYeuLK_of)plaABbvGN7pNR& z%{U72Ucg$-&{$0l?Pk?bbIs14ENy#qJY~0jKH%b>>Y0-wVIpt28-mq1Y*e?#5j7tX z>QldzhX3ki7&~cZWbI0(fXtG{1(IUqs9>QhIY%8hzPa{vj1>m*y%XVxJLz0O!p~r+t9G)`Q=WF>8BMOYisBcW$=(H|Ntz+1{0m$Ia;%79*~W<( zjrM1=g9Z&@u2M^y@7O2&7}|%8r>BYmugTif^@nP7w?HO7VJ=8*dcM#=qET$e8$mXS+?{4J)R6WJw2yg(`cUajCnau44Kt+oQ5;!bWpe9ZAYwCLn0^~l;8L5TqaCNa>}0Tg<> zsmvXn!XqGzr5=rqbvNb@viV(xLI2sa&=T7s1+Q)@*(#eX#Anl5@ukEb(G z&a@~xG%(5ze%C2At^0mV2Kv3n!>%Efog^9QX2T}gc;%ix_F}xBgvi27dBY-FjHEG) z>TQB}#BY&p<7bppPo*e?8$_efxsKa}>Qj}ZVStl~^9P^|ib;C8TQhilT6-G`MiJQW zzdqOp5(;PE(Z%WJ()gUUc{FQ!JxEEac@>leBr4A@L;X6__uG-;evy9MS_Y%krjtf9 z@*1Bq2EtTGw`gMK+NjiiSehAezV74PScum*HF9~>T~5HEjrkjS67l=jv-Gpa`kb~& zQMX-Y3}^dE)=j8>WJ!)jd$o%y;LZ9oFeZj1ix@df*zeBG=V>;c5p1%fVUnt_H%unV zBuNInEFYbzzH`(_x9DL16nmbZg=t%h1}T61qp|SEO@S+pxgnLP)b3$f8PuvTc8K|5 zidAJBBzJ|4lP+rH>s#wtE;D5uO=RB-y5&gsBpWkZeh@2w(h2?677T#6fr7Bp{m4irvBh=wGHe;9U% zrSF!vdb=)$GE}Q5T=p12`~&SlguuM7wavtMe=VkL3o;~C!s6$Xr_Xw`_l@<6h^xzl zIVcln+F(myVr>}8C?Y)0WAkEC5VmnY{FqiUMzQ&P>kmx+u$ho6Bh;M~$=y83rCOk% zjeaDu2gEGsu^fTNkeE<3Sd}qM4;?u@jQN^jcXeBcmASeGiT*_WCLH(>U z-PQ$|RH;wFwdBK_MpEA|*@dI;`4dbcdAht9iRZXN(J~0wa>HxCJ72yWM=yINF!(@9 z0D%N$i^}==Z(cWWiqHM9mLI&r4D3Ta`ZZJ4iK>KMeT$FKUl3?#F=7=VU!$`-$daA5oMMm+I^&&RVbHa*#xNm2tDS_NJd5Rt3_7Ob}++ zyuK_Pr10aySV9mBxK{fbb+8Ns20^(pnSx1e+YfuWfJm0FBg=FxghBxU%EFDGy+#-7 zF9(+uG1p|n0MH{05TU^+?07X4wV{V=`jDf8~c(V044<8-;8_PAxd-P^M>Pc+z7)jy7^ z(e}%oCq$V(jXKeX@uS&_nOI9+sPD}Y{#lfERZ`-;CaZo=H#1cSFHd)%r0`j9mA!5W zgdLWE$6?~LWJ}Hska>=7FB6tpL)0)@Vujn%?C&?6TY%r~iWC6aNq*+z$0ULPPhcf( zL`2SQ+7elv9W3zE71>FiZns6@bhWdfdK!u3%WsB{Co-UBpc|bOb)%Z~>T{EQ%m>~c zj|9+Y=S z6~l_vhuPX97%VPHETFPGlA}7o{OiKkIVM-5TsP=g_3eDY35 z^RcK!O&*46^bCR-GJW%!=U`9XM@T=B5a_#Oj&b8P6wSbmFWSE66NyMKC_Ih)wU5Efo%5cLxyzJ7O>ow&3x*2nH?GP3 z0+NOKrf?_r#0%-RKg2o=>Fa)imYf$&Ala>fZgC9H{x#0I&Z_{3EpN@!%`o9uq1C_e z^rhB2{XJH|fR!muX7I7_=Xl;>bE^zT5P(04*Fa_2_YO=kbjx%wBOPgGGe5>{dcGXa zSIlj1u1dgnP?BtI2WJs0ouu}cD#S#N$YaXK1@Fo$F)G=aX~n93TEJ0JX`mSh5-fC zN94c$peYmoWt${H6~1grt9ewVstE*uqSKVpOzku5f+jL2DHn(_524*z=rc*Xh<5k- zZ<{^lnH4%(xA|}LQo&1H#p^ZK7gGMX_TgVOd0T`|x(M^+q;XiZL!3)V2|{@l8ia&u z%X9q?)33{6wpf`h@co`Go34}O3N)(p@ePis6qa|w4=eRr@{5!e#Tvy_R0nd)T*8>t zz90)7bw22#FOeQ`(Q_aLShEgCk|?qj4Omgk_-r8{l%^BIoD{azn3wbtM`S`m#)Al4GlL1g+)GjTS*~@W%g$&UweLfMoU_9 zjB`%dix@J$cxBtIwlA4wML%nfngp??lm|Eb=Rw)z&qE~q@UKq-y!gU@)(3iua>QH8 z8730PN3=N1q%M$s3#{QA^#mVUjo!RUEvRj_zI5K4DCL|pn;4AV)xLRKpENq9*2|c{ z<%@|t1<4L^t?k(>Nw2h3(|z8Z!@JfWbv=8+%$OF0Jfq3+{X&tpv5oJ>))w+;7_Tq5 z4aV|*XI{pu8&+G}x+f*c<{)ee^cOl%xgWa}*&4pw8J;>iN(l8bcmSW<@-bG3d8TzT zw|YqqtJ@W4Y|#znhH?(1YyFClRqEOYf=|z5jU%7Du6B=J`IxFnfW}HCj3&I|Q=sp+ zy%aT@hY*7ERFOfQ>u&RC;e#`4E!rSBzCRy?I0ycos{}gPtNP~KJ0hC}ZICi3N#KD( z;f99i+$1Yl;!f0&qP8cl`fM`z3UBygG~MVlsSX>U!b`;=5I4INQpTS_Q9~p}jyqFU zb|Cv{od(-s`a6w|TlItK5{%1G2)L)g#+57vQq?2g;%ZiOJ-)NX4wiFq@*s;09`K*l zk2u$*U5_#MnVas>xmszX)&Xw=!d@s37Q|Njw2AX13R`NAc$4RSeI~)f?ts#o@Es|z z0LVF|usjF=qloj%I6K}%uNWFol#N2x5Azd=;PVjkaT$;j9na%p>}9jbev0l~u?K<3 ziCVbMz6B5!T^4j9d|(OM;_9|=50nEir&j1V*?JH)+wH^Zdp%&iumk%CwllQC3AMh3 ziS$SWzA*c&g%CG>3z_7`d(@$C^Ub~3SM)2=;;+PNjV>mO_=DjBO?1BWiC^KLef? z3E!2QKp)0Olr@*qhqZ@yLp>rR^~@Y}#qEQ+d}hOiOjy4jTU!-H_Kv^^?PiZN;y8)M z+`Kr9PBHC0tBb?4-~stmGBfR@bVy4ct$Od&+jx8yI)n3JEtYU1?_&FdJa2DO6Lnta zErdiipd|mT^!Of8vd=@*PIJE4a2#B3H^X2!ZGMLd2>Aig8z_8HuhPnQIbUamVSW!# ziz)DJPf71EG#=pH%Yo{VBdDmlErrz|uE|3G1Gz`w@P)UV^Us2!Y98AdHBdLbz%SIf*d$iHIS;ISAc>?cN|oW^?rGK zybE~Nd%eBu7zE2?T=O0Y93h$w51`I-z$yKVYL2ijTJ7RcgKGu7#6LAPqOnwQjyV^$ z&IPz7R8e%P8J7Zs`jKsOz~PSR^-s*}ick_7`!k%+eUa?;Wt`()VH7YD(#j52tE!U-O_yF67jHA?PPSBf-WJ*Hg0Kb(p7vKiXG>r!GX z7_@RV9>;-fkXineB7PgrIL6{$B~PDeiKgdtriuQvWD#xOyg4hV0?}Uf%Lz$*8V1H4 zUYi)RI+T`N{@8{gcimDXI=TG0Pwzf@`3;mG;gIK#vQ0GJsQXOq_MyfS7tx=R0tK%G zq+X+3T{#g|p3MLUrXU)Xa`7s3YA8I`I``*pT|A8Nh)T@*^Ux{uvPh)S`jOC_`+%X? zW@j5dCbdjBo&CQEV9B!NN*fasnkn&fz8x=C+VS&V7E|ohYZyd_t|z)e^8T@3rT2K* zV179DXg^Z;`Q^iKY&N3-VsF}=>(N|QIOI$9sfk#?*l|g_9E>muIJApJks#p}Ie>ln zu|sj!mmCTfae6G}(uod<__{#16{z{GLbneljEvc6QP2-+L6c}MenO>{o88s13;zt=y zwFKk|@q+WpZK_P8KqjazJ3MeapPfW*lE=Q!b4tVFXY zn;XOCR5^TjgndgW7Q?svxdM9HFpn%4@wRe@T%pNAKY|4n%ygMvO=YK`D}t)FNG)-} z?Ibzo{-1OeJ#0Y~aQ3LVeIP+f97au2k2SxBkw?okds1DsI%V#U$Q)H$1Y_PZi8MI@ zO645pW2pA`BU(6z`zYmFYmE+jd@%vma}d3G#MQn{e&TLfzfmA{<5kuOkBC9Zx_CW9 zJ&!%!qv>D?6(+6U&{6}!(N0UcbI0?u96a`4{I(ONnw>Zn1Kjk2x!ctWsx~h`hngpT8hN24iR5K8gR|ogAf@!6|1S@G8J#1qKL!j>U#Nr(7b2h*xvebyOatLl3?q4 z37-;9v8inP2zt_^q9F|U?*79TZuB0#C-uo*;BkQB^1Ns3h5^O^f4NGsLwvhO6%}wW zf3J(y(lO?XZN|uN)Gp3%oSRi{hWLAnE%-ajCU_rqNX(LRXEvNR7$5L#qE*Al>S#9j zjlVDOicz_YSQq}IyTZ9zG44R-q8n!;8t}4kXCBA0Er&LhO{&`Ucz?oq2b>~df#I~j zfElm)_`KQsxJ_jC^KIpB8R)jA_;kH7!Em%F--_83Q#od_Dvnp<1K!LIkObwiAOMPH zzuu(^=caXoWkswBgDAgB#L{))SRX>_<6NZ|?Vq4Q9-^34eLed8H}HF4Ucf|M5a5&u z>c#arc*|_MSlOEtRv0Q3hL6T#?(J}{BdO#}!Oa^B!-r2u_>GXY;1H+hJ~|Xe`LbP! zc!Z)00@aVb&`f~2B49I;#<6oCwHXE80s##wY&1tu$G+|6+d?~6!>BtK$3Rz?9~cU- z6G0J|5XquYo-jNt&Fe+d2<~D7FOS0(=N=UMgtof{9T5HW*7NUQio0W}K6z84S!}AZ z?oC+mjBAcPHxW*wQ{~Y%;6q)Hv1%}Yr6a<2dusxoP?fkqZia`WxEj?&d7!~{(Bd*V z_q8_`P2e*xJ8{)g#>gH?0fg`@n+6E2lV|z4HnDm0q|bSQ3Lqnj5X0sH)2}u#^!UXD zhep}bO{Sawn|&zLC%0dsE@=Qr4ZpwmAs1A~&}A%DD(e$NK1lO&y8-ON2z}I-Jp}eC zJ@Guc?;N`Rs2NRuUbjO|tp@k(G0VT{h+4(>hD4UXfrbcqa`dh^=&xoHKTWjpF*Yli z+%e9kE0QtoIwx@v$)}pK`}lVrkE#GYDEzvQ8+8=;d1=f#^izdjlaY=-=gXx{S7ORZ}`La__d#NK5*ee)j z;n>+RqX~Sb6=+TYY5IR7YlHrPltWMW>j9$#D+ij9R_7aLJ5lP+eOMX;6X2kx&&^Tz zg?|&{lWn?uj(}EUm?lv)tpzo@i#2DCFT7(iAMw&IcZ1x)+%KLQ{XBWeINXva7T$tR zJcoHaXAJxH{I)4%?98KVx83w^oM!*Q%b?f8OoYqy&nJSscgrl+#6t7$N?U(X(W({x zkfmi@*utq{L5wX+6NY@x3mDxK*P-A>%lW-nDW;>PxWW?@_{Jmsk^rkkh`jaXY_-iz zINt2X1GmX6qE@q~)(#_(@9WNA{402nyr{-}jj9w~r&Ur`1x6e{dFwta)VRQm%j473 z4TUj4A)Ae&NloZ=+a&#U)pbEH>OzY0YvqUvOUv``VOO!micE5j1({(Jf@O?3Fx6~` zYfhJ@K0bmT#ZPNPRfl4e{~}e)aQIVz_!d6Yx=s zXPgRp!*LHjf<^kWoY>)gQdW-fYW2)VrvF7@nHGk=kBVyt@%9u@uPu>E5p8`(VD1d> z?l_#MK>nUXj=zt)OtL!zFmO};<=cnxV9{S<$JH~nhb3%(Iiz(Kjl+y=5tDC-+q*}a z%uz@3cI-56cUT|Q>T$C8wX*e6w(07YpFwEyQb!A2e?1h|{x4jut4ufgw~(R{W@WSu zyn%!v#}$E#-d|h&F{w1fAb2e&oSt&aEo!fg2OJd>s*MDE;BIb&M$Qi^?plG!=~l`? z%*YZDSEI8o7T@Y`Wa`)rJnZ!ztXP1)dkG>jJMOjaBPJhN*Rwsn5Y`@`Qqq_P$27(j zowbHm-3u&x#!;{q=ug)9@U6RaJ3S!dI5i{HaJkWGvZi)vfQ~I*-t_yYT?TE>uauZ- z%j?~=gwO6rt6yL50s;yR5K2RVdsMV8Tp}SAG>Jdyy)I3&bsx6174Qwg5Ygz-0Y}1` zy&73w*huzNDYt#<{I7Rhv}D$jEMp3ThnFeQZs9CU_8&o7VopUbgh$#+YYIYMx{eu` zckn~2Y>ui{&0x@QLVmb^p+JV&8R@@Qpg!;6c=ylUG-Mq_4~(G9Dn8I23daa zv%2~h=b0uT)HJR`(+8r_HPBx9%Y-KT>kdN4vH725=7k%L%MGHJY}(VqT%bn3*qZqA8Ro4py~LfH(4R&^T{H#nhC&?x+YI)wlRP*-ckuB zUfE!|C^8qFfh;af<9mQ?^MKe8=prJ3xj{EWg9)@fL;3T;hcA67R17gB7lq>3YcIQq zwYEa}M3^ZjVp@#W+&`}M#e_S^fK;^;i|4LPW(7o)JQ zcsGV+8(O{R&RkDP0v?&-;S`6MGgj*#^BC}zwhuy#`!KYbSCB?9@6P?t^{DxcGIy$1{IVW8=Y?D3LpcFsAcbnmh*Lci=L@WcL6)|fE# z?HPQN^|rOl8g*PS`+#jjwy^I;ufth;FSuE))aZWMiZGT8)?W1H##KzBpcP+BuXg4i zwngof!`=fr7KSak4&gyQa~w>dgG66oaD27KgzjuztrJsCNk#mD!sGGd8QJSQYmK_h zkBbti{9+`L8h=YeQ1DEN??EQdl5vD&0nb|`X2tS?5B9h@?UV8VlTf<;Qj3SiTs(tmU``ZmwTtz ziKdwy1>Up~-qNLi#H0O=i1%b<3bRUFw-s?4h4STrfpk8W#hR1I>wUyIWEs1@^ z;^K@6mXf+(?a}kQZuMDsNriSAU%B5K2MH;C5>aTDvKO>pmbt92@|(&2 z@m)ZzYG;N4XM{V0+wIeSO%{`1mj-jfneQEa5z8ZwkpH%Ll z%UMv_ZSCJ$J|h$y;%8RJ^FUc|$EZ`>otXuye3vV~!FsDybDJSmUyWAZYNDrfS9ck@QkM!lLx(2tb(GTP^DiO05DNnoD?@>kn5k`)fX86p0|8tEHvRmOR_pX_sBa&I$SH0R zYpm%8kQi+uJ&{eV>Kf0xqx1DzZk^x2I-bb(+e`UL#_uh| zv>iRL&YYW?yc1Qr>N}M*Uq5j~)&8mxz9f>)hZJ8hFB0j2g3U_j+fG71&Cn~hq4#1R z$50fa%jCA3{iJFoLSQNa9PaFb+9BA7LlHO^c?X2tQ2j)rKob5Dk7ic2slQ_mM1NO4 z0_%=`wKfYN!cu@=cpE>Ee;FS@BUuhwCXYBEamGeJ6t1-p;R zP6$z9j3LRcV5tcV;@j+#qkjMiencT))N65dbE6%%ZQi&pK?8%2Q5-kFMVJXd3AkJd z5E~8rHg2VlNEVrC1*?*i#)_A#eSDq zCLd;h&1l*JywLFvVN9DKXL#lH^%->FW|k5&yEyTo+;|*f(=q?*xfjiz<7!JJr}EE$ zWogg}BO=3(JUcjDte5k?J7nr;ya71xWqS-s@lY@2;^t3m&3D(TXMSVfZDhNQWvEO5 z6X9ga(kk-d-oD^4MRz8XOcxh%7#xOJYi*H0+T>v6FIVGCnbL9a4ZPc_38mc#0xkmM&RgL(3dKuc zoNp@+|fk!|HzDW$%1rkiCLHjq)Rc!m~rz6;KR-*{AwPmP5P z*E{8vOR;wO!`+VN2KgO2D8QMU7)Gr|aZ!u#p}53a76pL|ao&?URX3@t+4ri-Eq^#svhP z5{@W4Ec~%!xCh~q$Wpk}74gz2_}PgL1WjYl`IB1FD{Wte?|&p*dlt@8(%umzq{(Tje#~CM(tWLu>n! zL*xEu42jQR^b{A7U#@VKpktKc(q}s^hjE&Pb-}lXi=aYL|K|uTaD*GIjFgNbbT48) zmQk|xk=U=TC4V!R1xY6u7Udgo_VtM*PvDQu9P|B;*oC|Kd_bHDd)#Q2Q=d#FM51tS z1%|6&JB=@5DkW+-uF9TSlZA6u?5N40-a|JtES}a!!Zqg@?uV1XOEBGOxS8u{*`Gg8 zF}uZOZj2o{+!M@sqdl0bs#>~*z_XjLR<0RHu>dFL*(5u|r5!6He&r;djHfY*wrkx> zEQkZTYY-!sHmRaH4*Ryl-RfZaYY!8cPc_2`W-Wwx0T{$}l@v~HuE$(ua|5##iOIE_ zBc?tdp9DOmVp&+b>-rlDu!JhZhc{3a;DgOmVzpDd?|xhq$S%(slM{O_5faJEXho8b z1>qMZ)myH9;i+a433kdx!naYn&;@O_G%Tl^>r08%bZn6qxPR7 zJ&*f##21(MX9iNWH<}2YyUGoF-FamTi1~v9O!53A=W_qsIJJuYbAG|^>V^CufS%ne z6?Wh+B96CehF;ArfOyWrx-qp+6Ve8%`_x?%CPYibF33D%Bt%GY9Edy|pO&@KjZ%>>w*_@| z>q^^ksHZ-G0d+B$d5O~ODvL7Dvpg-;Bgdw{jvM5QWgNKt@gtG(sONK$H|ehKM_mu-H zD|Ez8i%ZJKEW^J%zwFp!%YB6*wGsh!kU6x34xYa(@#umCI`v z6I^vJ?|@u4@k3XFYR{HQJvpzmKQznbiU`egbA-TQ)Fk{%np{nY6o3O}_Goc8JewTA zh}AvW*z)1d^0>k<&oKDzZ2-6CE?a->H?^ev+V3(6gW*gf`u(krqv|2I@$57vO96TW z1M`AvYJf`t@}!z05E`Ag5>4?kwNlk|))bn#Ib>i4{>}^(I@`I1k3GeJ-n)cX2PtYD zID?5ErQD?Ry51!lX@~;uVpJJ5m<0?a0QQgFvllI99!o2JukOED=u|2E@=16{&Q z#P8*rno+P9>u*GpE@qI9(2?WL>{Ny?b@kzF4DBe-K7_?s-l~W13zmjc)j$dJec1rh zO`O*gp8}X$H2_S5-03oDb8TnFaPF&3!$pcp;VDs3Vd-KmL=5T=>IAQAZgy@R{+jv- zFsVb9UQHaG?|7rddIyHwk%xRII2#pkFNg$7iQK3clW-#b^?FGS`%t8y9RKMWW0^0t z`(;$>jsm7A38s#-;gjCz5zR3-Z=go-$4y5=#T;M#(k&RPWagXzlC{1IuWtiC`OpfS zeo-dM9_Udt@_`@0P!0|hvSX*n&(Jh5m+=O$#|n)P4LLJGVyU*SXc1!Qs8HnJI4L9wB^H_(&9HE^AD2MZ`+{?w zdH!Tda@n>o=#!0X(1#3=_9CLEjY+vrw8vm;{FoTGhTqdoXhG9de4JY1k}ru%+NM8G zrB&}r!{l$f%1@()oJ;ZE>m70M;A6c$1ClQJlflvHKAUk(iq= zU{&B?xygmd*;MNvg*>G`xaPV6t8DKi~AR?G(@gUbMs1^=!ql}Aiw*bnel!|jg(`{I9$ z_7XANCZovDuM?;45Y)=pY*e$a)=^R-h53rGoeD5WoLnhjf1IDlg{763{Kpmlc;r5d z)Uaj2PvO&IFXw-Jbcg^6)(OaqA?DCNZF(ZMil-<75eKy5>*shM(mmXDyfPK#_?I3F z`Z{FV8yI{Vx&J@!A_iGgfDN-2v;5D!zz-ocfUazL<$JG5^t|@=qHx;2M@+{=mwK}F zXZXM(i+b&Tm%9eBL>@r7BR9ACU*T`?!a;(@3LZ_2SX+bt=|Rh0I7g^Kce|y*n>hoP zcd#w9u=RIU0c1R{i0gwXDNoFs)0GwiHc0A>|9Hj_&UY1+b!&L9qW7!*e_C#+ND&z8 z##fwt0}GFA25}Ak z`{Vz1=Kp^Dr3Kq92c(t6cu(H`+pPcl27dL-eM_pSOnm zTWqz`*Z0WYKab4Qr}#i2_i(!Mo1o)+;n$mH>m7Ql#p}1)JF0ajER9)>LwlK3kIt6Z z1zX!cA3oL+Eb){ZAq{e~8nJU|!O4)qhe1Qa%Tbf12PFBzLw^ZO*nYD>AAm#*SwZ;h z*<;RTbk(l&$8319MdyvTLElZdo@ya&`q2I$ZN>X+rfg@{OC792?NL@L9TgRIsqKm} z_OG4@`a_CpCr9}YM)f~U=nNwNU@ZZpOoCr%v;w{hPh&IbQeNq2(nt~e=lh;Lt*KrX zz){Bht7-@rYk(Js1=}TOK1s@FcC-gj4sKCjuw8t=SIy$C_1IfDYB~?{` z$;n9p=AcOPe|5gd5-RIufnB2(<9}QSo}(!sO+ay}@5O}T_+NSOJob5-?qKh_sSBlz zYl<aa)u+pJ4W2nvJC>#iS~|7nwmhIn3Mv1aAVo(#32JPU5_2{m=tA&*W&DT_O(yJ2)lwx3uMrVDqa{pwk%jzLzARHSK2Dh+wyL z63MokZY#cq9-IlMeouHJ;i2Ge;e>8xg@j0#K`;FC=NmS&c5x^Xr)5GM_x%P9&e#9| z^{{VWJy`}3VdB7PtjRwVVWmM;ERTT{H6Bzyb@53_gI3iXPj|^qoEMlRP3df|{eupm z2cAJi;2^BoJ=dBqlv!ojsmP%dvfqzP%&6-;CFS{QyQ2YLCFAj#0J_dj=Q{#Vle9I# zr+@EaD)u5K6cRrof7gOtbZraRc+Y*j~dF`OUDQu;!1|E5l%sm z;4@wKR2S}Fx&nb9lRdcrmoO0xkR%%8uHnlmtWyZ?EgQ%Qx<&JE&a z(wraB;&o9Z*IG`wvU0y?s)3#Lw*ZY6=Ayeq66)~|fWXp>YNOhB-D8`_Wm_5uSp2V%`S2Ehkec%Dzj0On~N2J!6fQHNfdW9s;*TGnig=0>l zDALi`?^=3)&7pa6G^-7RMshfGnA`8s>%;aMd$a&Q{oJ%O?X^SfzqX@Z87DB-Ig(9W zULI@g^kOKcEIG6weWLHk+Ui;lV8i6`PrVug)01PfHf%?s(rXT;{s6&N)e%9cZ=(8v z#e0o5bY+hmIwahE3&q5+f znjKzjgN=z{!M{pWy-10GBqe@(9vI{C8t3ae?O>IXtYKI>Tx1De3MkU5jaBG?{<2UL2x*n3Z(P86lQ_pJo~l; zHDl3B-?`VdZ%f;l9?r1-3V>hnJXN&$CYNx5^Y!4xG^>kliO{BBUQkR610f!zoN4L* z-4|d&LJp_3(E%mnRH6aU{fN3-i6jO+aYLC0AB@D0mr(~;prZ_V*q&xIcKUGi6Y%!3 zVIS+10a%0Fo1w*SB$qIsaPe5}O|;gOSjGjPvPOQeiH!+dA!d&c>d!Eh9*rO@=@k6X zh+=ebFvea3zq_|z0`p}FDdoPp)jyxNKbambSPcI)>In&^X^_O=)7Nb0IrOR8|A%PA zD{ikg6l zkVHWrmS{jISKvvOrQ`CquJ43|<~K@B`#cKv0Tq0`_Z46W`48D@9!QebWW4P@OFrz^ zXRrX~ig88@o|lDehE#W4n8+)I5>hn4U^$>8%X^59FVHz8T6L3qi2{ym(AU1zlEod9|6tM2_I9-7t4)zh^TpairEnMSXg>aqtJW;5!MzG5_vU zA0uEj;Mb1f(*IN&Q6bFtzB)2M=RspBUmbi4{nP!v>ZhS;Hk+0p67rz7;OqS)aG-^c zsR>30m#@SiZkqZh2(V26DZw7_>ie*j`&!(B?{&1`oBW%~tC{ejdjcPeGlTp>0~%u& zN8ekYxp5tm7iJ1$6A6hGP!xNv#R;Av;mAS2!WD(a0>_-q`2yOyLQG%~F)*9YyGK?4+V$Kr+JQu^TEBL-dZ+jz42q-c+D3!B1`qCT!QI`01b2pn;10pv-3czi-QC?KxI2L$gA?Ry z?#JFbRp%EJ)iw0&?k(%Gg|SpZ7JCSgNJB@+zaT68yO@N0UbUxLn5qlnyrG*bTOZ&SS^DCQtQnJ0-8C^&;v%f=Zn5S;~-_l=? z4Y5*gM_BDPV!{g^&flvYV{r`Ud0Ly4`kC5gbd|9V&zFU z9`=fY1}lp=X&1UtTV+K>9AJ>voD|qa#uu_`rZg5(00&LJXQWj7apK{=8TvVB^yRN>2SlxI#F{~ag3fEg>!Vu$Jwk~hDFu+f%EZwv((0Z)I7IUAZ?8`+PLyql}_dV+g@ z%IBp{@A+7{XrVeszw;9CKnpVOQbrPzchl*tKz^<%AyG@>MikFfBn#W zS7n8tPu1A1SDesUiXd;LGSOh`aJ`_7VVI_}!@_`Pr3+nuCE>B5Q6qomvjxHHGRp>R zEY<2?ejr4;Op{4T;jW_uL#Girn(MXw55e-Q3=o0j>zOT-cZN%3sB9KXA-}K`yJzd^ zOyXcx-U?06Q_@o=zrjZ^%}GRxvQ_ndu!F=GS3L-jpccm!^}Pu<1}L zs*q==ng4pfjRs=J8Ku7L?MXa?wyWDyh5iTnhY_v|=}`_WPfG}*|_H7^0T zMn^byX3{^%4Zu&vscyj{u9Cji$yYUHrDglMhOat{ZunftI4oa**iR$Zfx%Jh|J*bl z7Th5ZwQaM|i3O-T3GodheT2us8B$am&kS7mLr>7ZoaTvpCI8D1pQC`Up z?;?K1^-n{XTlcg+pDX|{)TKl_2n^F!bUNnD^-#;= zzT(l9KO`pAS$`L%^|XM#)$DVV&PfpHtf9;OA<&PbcO&e{>VF0BOE!;JBM%wKtc=fh0P3n>e+Tmw75Jek_wA z<{WMvBZWh$R)ThPM5>mhavzPA|8bTc81lKO7w}@2QB+FAK9K0z?`Y3J&flFrIkhLW z#cN*z0a9{kQb6(Z7x9hwrt$eKZq=p@B?6?JfRXx5V(4HO0c+^HBfrHH5})A)QUcp! z`0!~eC)M9K|Fe78JA#(Z?3rZK8cd6?U%Ls7%t1a0+@_gt? zM~}-K`mR;fe07qF5-#0UG@ixL@CO4`Ayq5-6dVZn6 zd%k6#0B%$qCPG&%%WFZK-Q?cx&sVRZbjt@uAK;KMObm&jfRv+s8A9SsE04nxM!G?yn0UXp8pO~y$O`aX_2q+WIrYF^F^-i$m}`n$x>uR#v_v3p3k zv#p>7rk+MM#g;18hhBcEPAoYM4&Js8kXfGEP%x)k;;1t_9uYVOcnEXr-vX1G_{lCR zY=P#lEIzt71;m0LUp7E)I+`k zT9_ue2{3 zK{Km4dm8c|75|K71TDDO327sX|Kau`sOYpZ-~lfxC>;a&F$lZlLNb+5_`L)nHRwO1 zW4kf^9G(WDct6J929FQYCWYb?IH==Y)rS@ogeW7siDT*Gjoh31)5?EFA`M>aH7+a^ z0JUJk-!U($wss>`JWXD7$^_&oPMNmgW=C$ z7`{Kn9-ny_8pu?N;d%DI*&dI4Edxu!Fz!Y)og3XngWh@=s$9SqQ`LMBvRGZ5Bt1e) ze_qhIplXEt@1Y`Cgj>3MIK{>QqWm5hYtrtA#k4u3V#_!qgjYUsoUuNjPIZXzrO|0w zKw{GWKe3CzI1xhfnRhp(SAst+NC{2|g0B9DNBp~2+JArK1iT@)0lxKS|NAQc7h-?A z@GB|+%BG7CJpKP)azqLZAg-jOO!&a{|2ua4_ustzFi&-3Z*yl;^G1E>_M8Pj55fbeydgs3k#zw z&X$F2%f*Y7A;1URGo!h-sOu$&PI zv4yHlQqBfWOgiRdiC)2wPx(>^OtHNM(wKK#Rw7I31c%BGvFA%{rkv~4x_&zwtqqoi z1jpZ{PkwIq1f>ZEQq61k{GsRbd^4-EnIqR64@qiP`MlQP zaPv#E$Ov?=NQ!FHybUYc9|!B~f{qgRwMNh5kMLNMrv7r^wN}Ho!nNxgTUv|3z{}=& zud4&w(a7VEB5RbYflz&ut)S%7l5*9&K!}LT^LFJdpe5ysroO}z6)z@T^zd)avi+Yw zZ2ZS>Qlo=g{n){qg(*IAFVCAV`UFs_{uu5$Xz^!*AG(-zxe`3>qxT6}zxoQ?4x3)c z&=XT8;_b>CzNm^w@$Lje|Jw2k0D@dd65`|I6Hh5$JFf>=M9&1%<4okhpnf}d;306@ zQfBo29=dzh0*IXgP~>1<`0hnq-#*;kiwGvrV)$}5SptI$@7 zuXye675a~em0wRP_$L4y)*8@14*-t^l%MI(yIwor0*_j4P?4OYao#gwZq%NmRWa7cdpM-bnKquxUOWI_+G=x;UgG4_AXW!8DG9d#h zXPF;FQXKwdY?rVPb!8^Fz0oObq>VBT6WMp^R=&%B(a+sCSQg=x2O_|ZI+u~5DeqNFs zCanzJ!==-a4N&gdjfp4Zaas<0W*h_oRH~fiwvENklQlzxWyYJ{f33BV#wc`_kqJ0t z=I$(3|E+Z(<%Y+ie3%VQ;V|I;R+`bRiN)7h$s@!1ree@1@x$rbGTyRU7g_XqS!T}d zkgbifJ2~cuna_g!L~buKhRYXi|MHAv7~ARfIO_8{eni!5O$RWet|W}Y+ds3n@SK4|BY-YtTJrJ>YJ zJxkKmqkB)In0Rxt5a8*qG4dfEYb3ZE3wArA#s+QOLha`;=b}N`q0isd&G}iXwUD{l zuxFQyWNzBthF4D%nLrG&lgt*!1NxGJ{?MOWFVJgoI%x_*pxURCt`~-K{&3L#?-e`uzZlcgY^o*zO1|aRXuZ?)68d6d-zD?>s3$ zMyK8_j)E0E%l=&kV&7G576^>3RZ=~UCj7)YoFd$G&rH7UypWc->mX$0ew>t2P}!)o zf5=f(@Av?8j#ixf;hrT_^d_wx>s9Ft>fc4-g9)nzl=NZXLKZLCeb0*?!((;Mc-b^c z9ln@KN1O$SR&n>X$%MLiJjit)4yV9?nK^n7_&A>VqaAOQ9HdvH9G)q!t?# z6(3?-G?_aPJG|vQ%F-24;!p}sI&?^PvlbU|_NKzp^Q6MfN%k=Dmun2R$i-xBk4KLt zv%?`J`?MzL_SI7d3+@=kxVZf)n}+dggQ&ELWEja{U(7b4HU#?+)xq9uw-S$HKoNv? zpYC=rP8QdIu7X;HiU0M{Vg>`VSe8|FBDOH>mllzP;v^?})Tg9j6P2eYv39vs()K<~ z3l$+A9cVfs1@2f;1bUOe>|g{wB-}==#Y$OY0w?lzmsgQf*gfRFD?qGTi(st3lDNW) zDcqiu!R({QGo!~FU?*_o*ejT%4>Bd{sdI>7z?yxlsNZ%|hqv=li^L}Q-!RHBBBX(z z1>NOBGS;UZY83~a@NAYB^P6}UWpZwZ#H6Is+0Lpp?M$@#N}ioui%~fnXoGJsm^%3d z7BbTC(^ax+a{3pP>vBn{2$&apk~~#gpF;=*@OLWoWrkzxzZQ&q$T&dS5FbI4IS*sr z$nQrU;7M%3LxKqj?;(w@o~dJeLV&)&Yee9MGVNc9_6n6PA!*Hf*lTpfOxGXrc!8M|I#5VAQ882Ar{CW z&2l7c$!+$ezf&_(e=c+FFu{aIF<2>KKsoPn_13DKY8{0XM@KSa)C~#}=(+u|O;4;935?x(!L8+Mocp^d%tohw z1xELZB;uUN!V-M-e$Osb;Oy+A->uhePp=gYRU1ELD8M68M@677a?hW%=xhbHS{`J& z^apbv4>`5{MA|^sue1A1FP`&(bn9#K;R;rC*7|x&sR~KenvEFf?Yz7)=eLLcq-uBRPuOurAIFgvhtQk|ASA<{1}BtO z%d9pb{A{wkV?K_lNW!oB`SbI)QcI5VU{M0^YCV0m0P89SXBuky$A;Lj#Z#MhXoD9@ z8Pa${!SA=z!6NZLTU+UqxG-T_FccMw@;rYn$+Z|F>k7a{wgZhjso z#Q#;SE3K)aQFM0M&+uEXyoum$RBr5AXxD1fAlJbnzJ~-;^V%#6P@F=)ux$~gl&;V> z(1s^TtXIlGjP$VAJ$a(aNl@sY@%3Bx9bdOxEN>O@^O9xjoVv{yDU*14>oGQUc8bc9 zkYq{UrkrMnlFUWrOznT%YFwZUO?TvU<0_}2qM~{{O-C~I{%4KZ!-RNcwvY`Tzmwr1 z{5#_{fEF@!(*i7;-jK;%|4hR_BEEfp7DC^pIO%-6WT_^ndf$%NtD@Ahg+E6jn}Id={gLN?2CZ7s4+%n=udxUGcVY4Q;~GeZ@Y(!4wdW zinGtW8(?F$rzKE_%-!Z?^W@5AzB6^(qffK_{)8Y73#g>XB*3rh=hz~7A{G;PU;I; z{4G7*%N@X8ko|7<#@l$RdkQt!m%N)V@bU62+;zX`^+s*E)w%3S>PNnsJknYhkc(*8z3z&N8LB z(lip!NBJ>t)s|{n%k#(eyUsg7?T1UbKm;AN*r1L6Y<`tWAR<`RoNS%{yctTDx$3GZ z7B1%(ua?zr6u9C?7oA!)(QTLBo%_Rfj5qBEE-RH&Spx`xYlczM640cp&2w}H`##nK zdE=8ecuIM+eer-z<{}^ch+Fh1mhtapj1Oaiauy@^nq7cUfQQcol>2$d-E~Il48b5B zN_1eS`@#hKReKt{81xkNu(Q zg8)4*q(3Fb{S{v{2@^y!HnS{Au>Ab)iMM#ve9C!pdcRJI86K@r1FeT5Aqb>JYb+Vt zAzdZ(O;RMSnNOS|abVU!un^1e*!0N(+5H0nCgy;E6sEK7x@jcjxF+QVgaL&l<)3B9 z6O)rhuJPy0fDGE&(+O@613db|RMX~n>0mSx=8r?(ovP-OuZ_6C_k~9tCWZ%<_JUV~a>u}Zl+B~PkkK*;IB={TxDFOdb z6t$qm=~C7Goa8)=#_(Y32TK4fYCNEv9@!2dsoa)j&2+xiW_Re9N#nPzEH|t(uxY)d z*1mrvKjzP=%HV(2!~bX=nH-l8EHgRZop%5bAxTBq-=&1E(xyf;mK_VQyjJE*)Ie*^ z;thY;FW)Qih-xh?#-0OBB&c5>AAFK<4Lv_0!~;#(H2`!5i-3vP22Je~qCRdqEft`0hb z4{H%>bdPAU1pgr{fD>iq%)9UR*tjh2R_u0l4#e6$=FuK?GWqX1b@W$^wMF9BkC* zHLm*D4;Fk5lIPGtH1Oe&s66r8@C!L+*9#dm26(}TiHg|74g~aQg<?2b(lD zKE&=g!z%ZOi_Qi7g?k-p+u_sll&4g*(Bs!wmgv+}0ry%R%cn)kCE%K zm+v&V%3BhL$$;_Ay?3i%l00!UuU0e+^b&BV`mF5=mzRD2xM(Y}Bl<&F1|)RdzhmWY;?!4(?&%*y;Xs9>-Q( zrqA_0qOGSf0P)Eo@I!6CjsNW3as#Y;9DFo|Qa+&%B&**q#B7Q=^LyPd>!p+nnm|Em-BzbCfwv#_jKzO z?Lgg=EjOy3WT~grAYhx0_CsDqse6^9{mZFhL{JS2RiWZ$fEZMWYA7LlaShQji$*yn zQ%*AdUZVTDFUA*y7>2b61p~i=)k~K1LI@!`5bAzOOjH#STa+cfMJWT56n7biJ|||R zC|*RuTG3tdg#3O0?U>Ww$&L=}TYEv$d&MX-OyY@ILG1OJSqKYnbkvg;b*z{skvW!* z$4xJcMju+Mjg>LxFE+Qyip>mr% zS5hXTKlTS0xvO za&o48K|q#|`VzwX69!r3c!V4TV`EqcEUj;Xehr_Y7Li2t;xWJ71h*;;JxU(pZi005 z+LJ$1Y=9N!r1TCZzMnJA;UyoZbf-t*5U5`)E4itYLp-nx5S>&;oXe;lM$}JBad^4t z!HfH=6=CK@&*?;6hKt-fj2dA{6#tBHDZ2E0hR58!mZUr|gXD^=+Izneooz0O&+|+6 ziqw2Kq(D;6e0WlcLObS#^LHpdWO;L5Ir0>Ty3%!SWf~h%L0C`#KnGQ>avSiv0k!$pE%Y7{?V>PfC}S$4;t&yg6$VV? z^nAPvQ|$M2J@0&`&N6GQW=!1A6+J70lLof)hlZE7DL~iV5Du&gixz(IftZ-$t%d6F z9A43b2<0Thrte}(*cFMMZWqZaOtd_jIRa$TJNXy$p!a2|WHIjL-JuUR0=C8N+g)kh z#)?GS?K0+hm9Dv73gVSBNo8S?Aw71)J|aE^=$5pXh=_7JEKgz-CI&?} z#rdfaeDJuN%SM)R5p|-kQ7^D4q^pnNg9TNI1o zw4^3)NE3I5MN7msvIqa-`~n#->4onPqQKAamKqN~E){4d@f+{g55qj;gCWGr+W*y? z|8%y3Asabm>D3vkyqr`H1oA@j^j_ec*C-%QWk14P;`jf@7kCEnh*Yjix|6^fHI0H1 zYHFkhO&(xs##gF}5&0nBJ#5u|o%82}TaIl(lg`f&8S2n4702nxH()O#f+dCczIFXQ zhN<5UR#l&WgI+Ptc5yeB-yU^L^)t8^D-*m5;Nyg|rbjGFV1Ke;* zpfT&Oj&!9?cq+D<6d1&@GAdN0#;qlbWUth)LX zk=J~O&EfFw+&3P%70G!mL)LPAV|oWYSuZc7Z-JBuGqg8y8JHp+Q%oij zhd=?h<~nhzty zsJ$=1ORoLyE_-|bgdYrz_$-r1{~|MI+^2V=@VxwGZZz2JJp$Q5#WQ!gA8mfsYrP(s z>~4+Z8LF#P4$|m8^Jx_@tKTqGGSdIgnF*d}et|^cS)Ns;s-id+RK=1=&&cP#H2le7 z1w&d^nf9pOEVmSW;N1@#Q~?(M8^m1)bfu0qyg)ewh2Qq23mocdJpFf-C6GH_e^R}m zo$@fV-e-rXovIt=2_Xj|t^9~L`Scwux?oH%jb3S3FAUJ7x!V7pAt z@Nx&Et>39lo#h-H$-4s|me^7vFImiD0ZQiur({nqj@Wu${Qz(9pC9j32A~m_%h6Tn zH=9dZXy;HKe!<`~q^}UjwbFHKC)WKe@MYV7!@mGY7V7D~s->6`U%dwnG&5VW@KF(t zj*V@8Q#~Eu0UWb9InyDZN-G4KF;d=!)d zBQ1ymbLE$oiFU9*N-VEy>8&VM3=VzQzPLMt0R*{v<;a~feP$*3RnWPZt=*fFcV3;gxnB}eV^KfbFRd5Z2yG_S%F)iw#)D9>Zem>I->1VfSULYCC2 zWTNdd<2vtq+ss~0FsqiRLX2is=_m$=rxAusqHt+xR3<4{>ki$rNnR=Nia%z3coh%p zMc5R5bToCe8B8g5I$yL-c%AX>9-A|hp&#+Sr z$xO-q8kM^`-U+&jo1A*Xj3L|V#|`ZqhJ8J=;HHVpcF7MHO7eQSh@n*Ysa8vRVTM=n z~EuC;aVFCz;QT62&sdSObovrK}JWku)*;Zxp;PT7U0 z#1IP%b+99zo$uQc|8sc4LWg{e_HF+@?+P1qu0xa&#{#2Hrl=0o)U<@qPi5qImM3{F zlGSdZ=f$yI0+)dq4r`YVO9iG>!8baiIULe82QYB!p4&!CRWQy_KBeNG7@SP}*w;8j zy`|BK7)1jYysf$W-zo@s;~6rTMq(OUlOqPd{Ql|3io6I(-g0pDt$+%$h^c4BP(zJg zKNXBFzSQ>@CAKEDQr%D8K)Y3|(Rz(^SD9Bk+!XY#qwj_mhwygqe5G~Ye*_HdY|3hg* z*yF=Hq7ELb^LfjIF?gLGWG-4U`PP32$NHca=qj5BqzdgC!0oG5hE)--*2D z!>K?9Abvb*_4Os=_=X{x@x)6GJbnLMMek{1ng(L`m zw2Sfx;5lUWmx3MWlhO;FMTOio(R76n9I7m%oOT6GS0k|%n+ohisE*9C--!MU43_LN zZNTr_ty-+JeLfX;<3A=33#F-tdUu8>l(FR{@{)|PPO7Wh*%;sbx!UnsQ!R2c(#Lr* z)#|#9(3QY*_?R=gm8F5M2)dw*{OR|B9*&?>>Jq|!kG!@1g&biJ=6ljnPo=#9JPmjC zz&uTdbk8;|xBTvTo!!&WtB@^~=GIh$wOh=|_;d?j$orJU$7Pa#U@1N%h(BnnvD&5Q zCPRiIfF}|1yB75urGdo-NeNKdOSFQ~>4AP8gpDXmBmM!V$mu2YcBgH^h9ozf?~xPT zH`XkRc)NO~Sh#1K0&V{mZ1*{$U36!?$5x|tnO2Z3v)k%n>W4FB+i*uZ90P=o{*wbY zhRwtDF0r}fp*{}>iFm$Rtz#(PgsU_zgT+b^DaVpS9KIk)FP=%3mX=jZ)~^bs4HT&w zwtR{CjIA_buS@)%JSYDY8p}e*Smbh}#w%prH23VQk!-<9Gqf|KnvD&Fp{8a?=6r0U ze6-IcN&FG%!mkOQg-v6k(vr$JB7WEEe1`=*g0cr;&R@S&qa^cNk~lMIB5CC)4i??n z1MiykIdN0Wg$O;RzaC@ogo`=^?j?{IIZBtWC$)LSdJ#uVx10eX0NLAkzRuN`z<$_ zZ;klK4B;)xFWQM%&Lwv04t@HUJCNMaY4*+$@~hVb7TeK-ji2j<9^X4|P{a-SJpa8} ztd|+TSozQCZHOY)d(PQ&B{}b(lYq5UHyZM4hDes=o&;ue&LW^jMml7P1>h%`0PFGw zolRdqK9^?S-nG=PD#C$Q>trHsj2qv#qTA#-DR3WL^bioash-&bzt6s#t@KOB>v9s` zUO@mk-O97b*3-Cz=gt-&YN+m{&h9q$PwcX{QTHT6;64=&uA{7UM{oFN_YRNp4B%1 ztuCn0UoP5&hqJSu5)M8YbSeV!5>t&qAjNPzSGCjPy^8@<*u`Jrrwt5<*tdwLT8DkP zKUK7NefC3HiXXTwbh^G@h=wKgi*U`40BRESThpZMQh_8-Oq;eXLscDeB%JY(NCuHk zj<2avw7VX$+6WLoBZT5=Z8vJQ&tIRuxf9=v@J^82!gGl-}<+9VexM| zo8V0-Ow0WtT8b6qigH?MOzE8w@WQW!k*Lk)Pkjpk?{qu9C)Euj@W7i4(FtD;Hd{d% z7NIzaVjuyRm6pagZMy3M#7ng$R=LA<%k(U~?&_j?iRYrv z{#fc`V7EfJ|L-2{tV_=|Q|O*r0!eAa9HBHax&mZpZ5Q|FfBLguKH-L$-_| z5Qazwx=SYEW~Jncqb1MuNz2H-zLff6nTkw^Hq-DF8^W3HQ6n6fXsu-sDhAdfNp|nf z$4!N*)y-2wj}l;yLnqc${NNtrJB@8<>-_xFFKGY*O^9dO2t1M%@;P+zVg>$j#h z_eK)baH#uP*@ei%9s{3lYc}cG;(a+o7N80wL9h4kqZh|aFcCwjL8}W6?$>eB8lsEN z;#y%C$qQ(JB)px%0B8qjAJ5X|eYD6d2n&^)XND3@(*q92P6F2`c-*Rw*b=)-$fAGd z{aM6Lakm>Ss8p5dql!wzk6O^L9doIdnD7>aY!Hw3fx%(1Ly2B747WWLjbG;N^OXJiX*S6C zYfs-{?=>tMp}*^-k?h4YR5B zd!p#5#sm1@1KRe#0X4-sI)uyZ+Va7LeaOhnZcOa@QiD0FOe#;gp_0(E6LQ|r));r^ zlEVdXB!KCr$fVah{>3=L|?@G^CeSnq4 zR-ln*uo@ED*GB=GL4kM$Bf=d6L(Qs}a&EbwTS7&w_A@|!G@eelijuP^L6)bn02L=G zTxPbIL>Z1!h;ce4v|RO^F3L};q@r+mPW}s?_rI@FtTChK%MoG#+?G}#6(4|7HK!5&zBaPE`P0S&9*U12C?co zpC1dwzFKIJ{tJI}B7?r~w5xYJ#2p<@nH*Yk0E`)a&mz81QmCEBPi%w}4Cn$5vFWzE zeHUW2^dDd+N{dCb!*j>ZArkHlI{uCUVVcZs-{?W>E6(8A)0Fjvz8uX*v?G>)5ab^F zcjNbC)hL{>)Auvq2U(b9Vft9AM?QI`=2-kG#6-m%tZ#6Pf9DkW0k4~Zt=qWc!^+7Y zsK4R^RS8)cqxiIBSn&gw&eVEccm=fT+3h>MJb!lu*!C~M_5ugxxSf-wn8A3k*^4?#&fiGIdaSUn;`DMmwy3@L$?m@Vmu zE;gRqew-LjsjYtMY&ZC+MEHx`ZQz}lc%(38cmDr1e9{y&H3f96I(s3L07^EN031lGgsnX?- zc%OHA6_6x4rAOWk)Rfm_$r1iBizUFiB}eB|Us3Bvz=KPIfhaz8uN{lyq71_ONQ$0O zG&;aTNx-51BpF|UI)-1#d#GxA7JW7MSD_tBzZ6eKjVVFwG%#V#>)4ty0+O2HshkHT zH*9xzPnbB2Wf+Uf_=54hsFqGGhp%2qe_nBtn|E7v9g-_y$$;aY6S7hd;3`kp5cB{D ztwu688&l;BMz10zO)8l=sv-Xy{?xd$C;#4Ek$?g-#aWm1n{8?t=A8i^_VhnC^nfO-H-B`CK z*QlAjF^d!~h>#{E=pm;a!+yNl7Nav+u;7uSKxudpV>sUePcwo?lp#(m&FTx$-L048 z=}lJX=@xg)K|h>piLR2>{op6a@~d@VbC}2BQT;ojNn`u;~#Jslc*6l4JuK?nO8`cwMe3P0I+d+C&LeQYgP z$SH_av|j&|3@I-2Q?28n!^L0Nb>tPBkhc ztFMqumMF&q!Oubpt;Mojd8F!`=ex)K2?+|~_RHyzT6EF`y&fVeKL8K;Rrg13MGnESZUFEqw}v0V#q=%8`eqoIL*!>`ANyhY#u zCLJSqfxEjyGvFvZ0aCISr62%mE6@$>;aiO-qXXRdi z(WypfdHK3OCyr3St}p{xA`u@r*Yy1+k*r$vIA~ok`A-0P@*)#&BX`wP zBi7&(ieI#3w{!jJ+!Dkwght{swd`=IE z$v@_GAUkP^<`QZpf~AudNcv5th~eq|BQf-XWNPuJPEWHrCXw&9F^+{OqNwnnhz$Sk zQjIdR(^NJal{6am25Q)ll0RN>fkpR$Lv0(`$J{B>OEC^AB{4}iiIm>YH7B$sP+ou@ z7dnhY=WDYw1f&m<=@lXO7T&_4as&oZHXOIUN-&7t^Py#p(+gMkH5jBC)A3?=bRJ4e zW#d+O`rh=rs2u@i!Rqu*hBS#-9qQgr3i{)F1}ctJM8BK>vG~YEo)coKFdn9eeJ5eu zqwZJOJR%i|!R-~n#80@o!RQ&42icbpRLOnaUG1+awcz}u$MN_KlOX>%&$h!ekL!$8 zFfpG^#uGTDYdkmc&saJVU4ri?Rg9_7*K|ip7Fn_MkI;N55StG<6E)u8A+DoGo7?g; zfnRXJ(55PxE(GC2tLaiI1NyI(ow=zlZ-@3^kwdcXms3E9l1yRzhaEOzsm-2xD4VeF zco@fv$fIr2`L^*~ubqh-zcZ?(8)d7`1ed|C6uv`2wzG~$_!;NSs2aKRfCT&xxy$U>;k1} z1$MtIyIT@qG9%PV*B{LM3aHDlhokX%WjWJ4p|Ghbm;1m1{;vu8ksW$yDA)l8CyIMNwT;cH_;`vXRzPw##1ih4NvsN!_AQ9s6 zOGEQ`<-;0?Hm<*MQokQ`WKE<(FI=reGa#TdE*;RuV_n<0#NXyLXw8$qGwQ<^#!cbv zR-KKA8))1yjmugU;F$G|huKMvH_i$%Z@YXqG!=JYj?;{iJ{(EA#iGa7%W0V+U+SswrvCx_#4@0mD3#S5S%~OWP^M|N0j` zmFfTPv0}*kuP*QHlIi~aZuA`8hD`>Rx4dgO<%GG(o_t+(YSA0WNDf)=rrqe7rss9@ z>+vRfG*0smVvWE9vm>i%@r21)MQ|e%9Vdx~8V;@@0#-bg`nwCmB&6cO_JR=p*!pg& zpzg3HR51H7!^$yHWFof`C`3;dOU{*g$laeZ7>Clf->QhTr*5XV@nZ>Uv_Z%~nMJ1K zRXK1N|1lQJXQojLZ+Uy<2+zLfXwl6D);c`$gCkafNxGchHdw03a&WD!@_M#3w&mxB z=EL^Oemy;${lLE``AJ!yt0T{_tk4hw@`INeg{T4 z%swGWoNGk=i~e{ECQY3Y<9JTA?vE>QW10?IEka_R$S~f?q|{Bgnb_D=rs4 zpp$6{Du{MCS?VB7gK5lv(MY51q3lM7lCi|IxWZ*~JY)0=Kp7ou<>RTJX|x*d z6pQyqKJ03}&&?!{9@vnR!t&Tk!+OZcVodc+AdBr zsE)S|4ELz5YtfB)BD(q%S+dils#33=i)JFm{y!E#jozwc<0VU5?Yvqkp#JOEH$o5A z*tt)M>i<5UZ~FdxJWyqm$P8s<`$wRO$;M7HHi&{wT@;9U7_m^(Wg2e8NiY2Iy zVi^89J52PIz+>m7Aj&>+hP?b!-DpdQT`c&F;b>??h`8S*~60_Do z#B0j^ACjgkm|f9;_cXV&!j$MxU?md!3|blPrI@R$M|MqX7QgnZ)}QBQQm2k3%qo z9Py2;JAL!4>5dH&sBzbOXavIp(}M;@I&HS!q>aX8Oq$mJ$XqZ4oTbc28w0ECB_sfE zfD0`2BYwS-x(J-B<*+STeUxv5%1O8{jDZ)#wQ$1#Ldkj1w{Sm&C5h?RFsWdcqmhrq zgr|T%Lb>@-SkEWfjtxq@_4kqX3RP)%D`MiB_UF({w?u)KrRd*Lf#E>o)vs&E*bHOM z-fRAT6083|XW#^T$G%-3CQ3ievR(!Fb#-<-7NXJVc5eG(3k_qqx_7v%8A>~L`MfRZ zH07T<8a~=P7Cm@oFS+276&Ps0&nP&K^@e;r6x*_wB0dbG)`MW7K_X# z?GD9#;~l?f-wfS)qdWWAKuR^VCml#M|hmazh8Yk z2_yi=kFx63o)RBE8B_eYFLSFAI$y%|8FmQH+F;|o&A*e`ZoT8vKCjklBDlc>u`8qP zBn8M;5+VgWBq-M8o0TiMs_F~{cn%Tcs}}hb-wO?EKP^*DWLa*h*c#l%)i1JZw|#ww zHoF*Y&a73&syeFZV)B$nA5^2W{X`vJfo;QDILIi!K{_ohZOB=ZV=lzQJ|9*a zdn?K1Himvtd^+pzH0#Kndy8i}65sSB;P^+^vlsIAMuNz?1>2l_xWKkK?t%ka<9q~K% zXRsUA86T#5FXntlY6;Mo>0RRg$M2*Qhw8ar?*4pH2AD%5$#%xLeWqJA_P?v_LMULV zJV*H+nA0|LkHZSM{;*mbPOvR}Abexmj%Pjx#;Y7!2>i?R0fvyKe15!HX)-upmA^Ji zq%{w)-ImfQEH`V;?Rq} zLM;IMWeN}F>icGl0!{IoD?Ox7D(LShRo=`4Ga9JXysl;v_f_Ye{5O@8zkQkcBnfg5 z&9*C~5=1VI_vfoBzm2-?=Oa5=`pK3Zal-UEfw6&A25Iy3AiLnVgX5JUhzvdsqNKzB z_h}T=*2)9xn5x_;Yxaa#{~1+ud+<2l^L_ySjaIw!{dG6o?C($3?1aI#^T-$mz@Fu+2fmEt@N_ zBv;y?b|cn@;9Hl_SsqQ2t1|2J-yplWKL+$4PKpOXQrfc<1AZU|b^>)hWMQ7%GvCA1 zKprIj@VS-&@ZaUI1%xO!*g-eZkMf~|`zHcg&D*zMrdkd!4jnjLmOFtRqA*C^T(*7Z zmx1{&i>aE0@3dp1Fn#Ym&(c5HTcuHq1z%c`|FNP+puiN~U#6W;%K0g%@_ia8^Bdep zM{F@7(yxHI$v%&)`H7(5P(6L63TqW6c_v)jGjfs+GN;upvtrTj-%I>W zWcP>f3R@PH*NA`-)G!%E$%v{?nPq)jXpb2Ufpzxpx6_*j3(f_hei14>Ng3{@Qz#=8 zVC34;GBOVmK@_rdWm1@($v5@Ear1|+9qLK+u9k3_rmoRI>Ef@&+hddXF*OZc5+J;F+3u?UX`M>$%^2@xj{lvm8F*SW zf`cYex$<785A4vRJ>Os-8UnhN+`C@_Z9Tp6WV0P}?zg_B6EeLp#*%^b&s{@?b3dzz z%vNgtQG2xABISDsQr*{9K7=bpptLw5OJD?#nqvGJ=WLqa5Fa*%{Ev-| z&3W^vE;)*ThaxW5v{hf+h5?gKbxj+)!ve+>9<-L{Ug6)p0?qG_wA;ePalWUG@5z*M zb4><)cD*BMX^O00pczYK;7f7w_r zbXq!Uif=$#1FUFe#aaKZ;nuW$?09QO+dm-$`jVlj3MDa}Gu_%hCL-A%AI3Mlb`?n7{g?Y?Ey_+9{a2rCXm$=MFwTJ-wwzRkY* zm>jpYg!A*OeG9JM;cl5ej-+5PWguoWl~%+(u$LXJgUo2rZ?mBUnr0*bMObF zLy!*AFLw?jIAlYk38Jb+h^>Kd+zI!pWMlBAt%CXzPDZxg@$XivT?;sRCpAX8ta(aiC@D*L+5UOi9^# z1X)r2bGLgO1if#(M&z7N zOk{bs_OYYaX0$KfcJa7=kY!T~%FHWx_2OX}loKfeYy1?;Aiai>A&pgnh~@*AOnO9cK9IL@JW!Tc zmv%PzqVK(=NpZ{Fjy9c8QJ758D+(|KHeWhkcApgp)X_P|fQ$wRJFCtVX^M7qcIpD% zHu)oXb$>{Lk#Nax!dl-yJ^rMBVJ6HsS!7;~I0}7S#Eye4L9frCiIR+c-0*l!0qaRv zs&!;WIRp5|h=eQzW%7#*~xXG?Yswl6GPCnd=n z(VAyX$eE>Nw*@O-S3`jfF_Xs>{uhhFY^<{U)HCl&#y365wKv`uDf_+M?d))Oo40xZ zei+F|gUnF>6hyom!fhUh!7Vn*x|9h*TX?5um^{X5r(S#PPo;nw3JPN^Y;QL#ng|t6 zpn`*TXJ@mnduOX?83Di4>w@tw)EX_$o?`0fqBM|RWewcKT*RgZzkSNe6LBkynBrPiC?5tY;FI$JA~2|G4<$oK?DO{1U@Ti^J0-jOXR>TQyBtR6OWkBT z(dV^|s%`R2?%5qW@MB%3lW2t~L;~3omoX#mgO8)`hwIpPB{@<)fwa4- zLfS_=MD%9QW_%Yvclwo%eGx&bZA<{20K-BbDip9~dJv7f3Zz z&%9!V+54*$HCibxb*Q8oRGqGlA;E4Mh)j&dVf;8vQV#1SsxjIiMlo0vG+!PfRZgtX zDC_v;L$ybO@6B4fQbkbvX1H8Gr)~Y!>L|f<-d*yryrr(*4l?=*O=Uaf(F!f3OmUyt zuBzx!{Cmm&NArD4hlDD}QCPxk)#(J{gBm(q_bL<)zb2kD#&G~nptY?MxzW{09m2jV z<^v-riWE9)D^vJyBEX~>Wu|@HD;z=dpJrjx7UVx@H_q1F z|DKeMi->sa$6!0}OyBf%=r1sZQxL9K~c4gZDP1 z3>hJ+gxE)s2~l&8qMchp?0T5MG#SlPu6Egl--pbpL@_DVLV|~vexwA?MhpKdp01vO zH@d|G^CklT4Qjw(gT!wNys!p^`^w6)Zyb#V)s-w(d!kLAT8)lMTZxSL&y`70_K8tj zeWCF4(n=Lorme=BC$nIGfn%s;J);ed_OAK@2_b&>Mhxfg>%__MJE4A6fFG;-h6#=#8O4o?Ooe&T=qK z#6>^xz_cU+RKiG~X#%5~3!>M;N6@!R6Fk4DL5In#7GogQ8iO9J>#^E-v@cQo@G|HJ z*=H;V@I(*~;)EZ()!r)!CpYdksC4=sd|d06pO!krQ;J09Bg6Lm2^Et;WyPWwU+OI-}V1eltmY9Vrw1 z5yr9D4C#R>R#=;K+Ke}i!6?&NV`)DN3MQ==e1x$|>tCQyxmIOz~Ifi?QQ=5c>p~wEl zW*<&nZJne{nBOZt>Xeq2-U1)gRj_QM8GrF*CK5BV zAq)x8SP8T&?FB77M|dZz|9sW+fDjf{Uoi?dhWiFEiQ>3uiw<>ykkQ*fQHJpyf-0-ndym08ezxG-ci{1orW6>e1n*gEmrEd z-HdzbHcW>SVf<^@yGeg6FP*=YfH0FcLfL0nj&O>rD?PQ39SU!^j5E5_K&BJD_?Ilk zsr0qbt5L0RxdahhnN-i&skAW|bZm%3?t67L&e~Vcrt^(@6)y=Bv_lKJMth029rU;X znQwg52m$cQj{YjTn#66GG~V%LJJ-yC@W`N9=tLgp^{=607a@DJjde@F9ykdoL!I_+DfkNrL*5TI`=6dy%| zb#7xzH-tqd%LOu-H_#aDMYlC7pC9cY&V;#Fq@(0%D1!dp`fY40K6eb)>u;AsMm&k) z4vgg{Qfi=q=W}RNpm`fM(`^0U7TvesE$D6#FT6xJgNa_KES^Urf&*RR9yeWz zL_7LF8iB}k?R(WlCM%ua<~8SZ+YEe;zae}Dnb+u{BV!fy;Z|>?8s8CgM$}m2DY6 z8^!?(cugg+Lagxj+ofD>x4(Lw7d5MyPi9ekElch%=7(cR9P2ox^?K+R&K~gmC$vbk zM_PAAu=peThhmfSKi-epg@2OOXw9^VI}P9-%>{sv>%9uu>@VLZ&a#{stbMZ)7Sxhq z(S<_87Oli0%<<@+e_RGm-{7!77wj|SLe4m$MNnOp$(f$q@$Jmo|Gu;IC`5^ZXe;xycSZ zfmBcmO2>gRG%S@czAx0Qvw-TUrTLzBelwP{_R zf^maHJsU)3@OZ z6*?dSti$30T-Q%__+O4f_j@T@yg%t%5{)eN#djo<6^#tvLyISPR8ZQFm3%%kEH!L1 z#|O6Jp}B8ySAT&SJo4U-{4Y1@AF=E#BT_h(+q((wH2(6_l9FPB@-G7a9t>VfB?o^U4?$e8pjOP^2FCB2c=vLzevgc+ybe2%4`ozo zmpb3#4ELLo4>f$AQf7Lwq=;ncwV&pABfC4~%Mh?h!WRs!!S^sQQd@oIh;?$8@{aQmuY@^kuU;ik?K6y1x;E zcC;aIen3a8820!$3vHiEQFBz$-@cA|Ys^y`N-&CPPtj#O2zRdPUW9=EW2P%m%Olu* zLWRN}C2c*=~`*KbvXBOYxK%@ydX^T4~U0Nn{9iMhk3$ADsCM zM{xv97b6I8!dPCDn7g#r&}*h>)3IMM%(7x;jjhqn@Mf1B~Wqu?4RfJ4-S$Q)Cm~d<$E-Zm*;Z_ui`(!;e6_4p<6J2GQ&F7 zi=gvkwCle20;;{tx=VZ;#N%>W%MTV>411sl zt7qkK2p=GcqeV~3Ua|Jbdh9f?gT9;h3BJ^G%8L0agOF#Al8I}=_W?#W@(ys*!So6R zaWkG^GUJYY%C|i_(A?5PN5`iY>yCt|XYN+(_NK^*5W)`nQl z+%LP|>{IP`u|+=Exbuee%~H_{bY`zf?=D-?i8mfhp<*<7DVNz8Al)$_vvBdmK)N0E z8_Gi+Ye_4FOvt-QS*i;J=Q>sGVLfzD&n7h7*NSx9)S7&nk6AOBO~OD8exz=F?7IJP zJ6jq(N|~e0sld za6YXp|1|(FV0le_0&}zhY72$=yoZuiUJ_DIgn!CKfq|H?nL~=V^`hBFM4v7arjT$R zW=?$&QV|^mZn6?PY)}+CScvV1IUCvDfte2W5elZuavl;X9O`Pw4rrTm11o{Mz5K0rj`~Do?_hPTV8`h^JXQ zG(zjdH_gLR$sRwsbTB{;57OX*m{gv!X#HQt4O~%*?%_&}Ek`zP;_WwzW;2-!Z^k{b z@77Lf(u@`^mU1bMUpvrONZ>L8hwnvruqh})upZ8TPlrAKTlKV4f_@-no)ZMc;?kT@vVp$(&hrjBM9 z??LuR_biTet_3on`Ug+)gols#PXnX(hnw`mIQnul{yvcGj$i?C|rkaB7!qEe#et$znn6Bs|8OM8D z@u_axFY3I}KNn;OU+OSAv3emn3&t8DJ-}&j+RmolkgAjIH~yoU;nx`p2n`CjUeas+ zMaS{c`PI8;LQpV6#Y|cf^LvD#;UM)L)-mU8|0EqdOc{$_H<8`+CZ7y-9066XYtlr2 zhNM{UXxN=a{2z%l{?W{fW7gwy;8_uz3kJXCt?+r2VPd*6Q;4lIF}9qSH5A{(_euCf z(hE)b95iS~n~?!B@W|11R~W*tPr?d6EX_}@@0rsW_5Ef)kwJqwf<)JL5s#y?k*#O& z=kVUWQFNkon5u~H{r83gg z)l@B&d9({e;5ZCh^^S*#6qZuEk7$Q)_nBK44`{z<#6XtMmEi!JmiL!G!rfT;>ka6r zv`2-Qqx21(`ykXSdeEXOs_?AMCd+CXD%q*u@?J5hHL<~nk$2#acrGyH&Fc@|Qti%H z>Tman{=tAu@X?9^4O4~M)5`|_dYGLpf()N8gDm)hW(Gd=S4c*Eo(f(1zg=lJl}YP5 zU!zb1p)P1rg{An_$rm6_bP=%Gv!&Nq_Fm`iBUqoRKs{aXw?EdPcP zV50*j5WMpsTxSd?<0rruJ z30Yq^t{Uf8e>K3c*B+)v3oRxB1;AA!vT zV~4eJ*%xkT&e4uX5)@^7ZgVR$sTmrNvUew%iFaX@9rxYpaFB~p)c;r$hHPhT^6VfN zxUJDMX*!4@M2<{%kamG;{8!Aev;JGORT>8*%wSZ3{{0o?R+i4(uR(q^&_#J8K!$6SkiTQ zehnpgmXkM`aB^x?ic;ulxm0jlIY2fRlrEv}(W0;Sc=);4O8k$Vt!>i3&!<1Pz@mCGrp@WbpIlZmF^F1a*|Q;zY`!bOu|l^VLA?RjqG2 zP}0lsO9nI@>_Rbsi2j1R;7{$vI*nJy)K|OGbS?C+|CfhA(%N9v;ZA)rc^4`;J=6m? zLNzLrv%Zgz!UIZZxh|804r`#}!6CYF8ESTx%Mr{e@NDSdf<*J?+KRQ$pjKRs-4A|)@dogU$cDzaQ6b!pOq=owmZl1V`^4@k1}HZD#y|$SSlw|&_i%G z$|ikKTQ@uDYca(arK$Tj{(yG<)3NhNd9~B2OVyzYObLXk{FU5)L=i`w6F;h-wJ@gz zatB_~pN^8x7KX(^mwNwLc4;ZfP&+I>C9+&cLd@mkH4cn6A+p&hTd8QAg>A!2oUlxb zBG++vV-d{T%q=y=o-y@TJ1*qL`)2?GJwE03pKBhLM#GHijaIF{{?}zC2MFmj^T=tG zlym!x)>kv(zNvyku*3Ppzb&TI+@6!B8sc%;;G!N1{)7=^gq%7I(JUt`@Y29<85Ah< zm@3v7b;*7!836*N5K4>LZdZ!qwXQ64ZP({TTAT|etA1U7@=*Si>T}iGS1#b7VX~Nt z_9FlBmIcO~VbWSNg6l366ap{sqM9mi08HSUna(EIe%lWtx?@lPssvh49@YNY1%Ww0 z>bX+K1Ts8coZOIv!ajKxrR`mAmiFwF2)=(e3&a91yCl5FPS_dWd)i8$WbC4te-|k= z-^8?n-LGPLOlKE{WfW`Z0=U$%4F;hDxhJU5_B?QqXZ8C1SYPoTcG@Mj`nfwCBnE zhGMI7bz<>i8m52P4B;=QSdU1}40nR9WIlAX9s3Ynb49Nc4Ry@gs`Y+rvsdfUWXo`j zW{iF_CCWi(ad|~ef)$%2j1UeLk%NIRqt+!$7Do{J^W5-mv*q33b#laFKBoW6F@17c zBggExCG6n{8Vx%N^NP(*`>cpm)}NIdsYur^(fsT6=Z1?B$;; zKQ-JDej~(QV;j2S5swvc;-&vJ#)Q5H%#fn18mrTltQ6gKYtBx!H^w^C^$GI(N~b^8 zy0}^gaabixTxFf(;pg_%r8fW*?)K+52pbT3aINUaZg7qdxDM_d{Jw&d_qK41OV)W& z+?fekB>krVK2QS)@H`Og_=0EHX2oY*gJ2rn3@B86mQ1USXk4}$jgl_k5*uTzD9jQC z`kQD+ojlJL0-6}P@~&2b(rvAc$2r%{4vD~L_5!|U7#3NDPJauU;XQZEh9-e^p6*FD zgU&QwBla2+axE)<*bsmj6CzaxiLGnFASp8P1xunVt2*@BE5}X`qR4I;_f2_dX=$Z? z3ral{YR+}YA}L4~AP*@~fR0!KpF<{&+pjt5XSxXTl)I16wyq8(Ms~9dpiC*qjl{}6 z=o6NjW1{z_K!lh+AwQD>2lUS~pQ0y>b>ne2g{I-&N!_&44~D<#_~K;l`xX}a9zYmA zEbf_3{rNHGl{^mF>0RM2j=K455288qOsI=2S-?;YMRqAJm7{H4Rm$s-mlP=Y)m^!a zc$q=N@81Q@6bh6?ylUooOyA0}cB5GYn^gl?6vgN#Ndi)236yi`vkapBLgl4yMq`4J z60)!Ub;Jbktwj_cE5rXl@!N~UG1&6k$Q;?I59OlFa!RbRhpV0jpv7=qG|m_Nc!sI0 zb5Jx_tQyNdXz#sH;P{kMr#HY*0A0RRE>NK9>Gqy%S{|uLEOtdIOMw>>pC2|6BQey} zRIHd|bX~;3^<9}-cOUx8BUCxj%}(dwocY#{z>}PR8n>1SvC+x^3B?J?XlU}O^~<0Ould2 zCpX-)ZM(;6)ao*s#fyc_uKL^h$wK1MwX6VPLVY}#=R&E!L6pdAJLvp}h>iw-vbj%O zco}we)ljp7#IDwG^JyD+5Eg;iyp5k;WwCjj^yP$Uok3+X%pjSG$I^{sycIoiM8MmP@{$QpZ2IRhjcTJz|5M5kFOK&u<2+e& z57VQxQJ3I^>de&R*`+EoSA3q2dL=aM`0&*qvMQuZ(r-gCDd;|Iw$=!zq-i~}5c?Qu zRTU`miH5yO%!c1tyiECPjXX=pY=#CY(9uNT%8Gu0C83>}&c8l10Qd8&t>*L@&LCP5 z8M(PsTt9y%rE|zO0PUf3BwFAD(Me(ntaBVH_kl@su|o%t^fLq61hsi=Jc7CH%kTURCp#a*~KYJFB&stL*p9h=VFnMbS3qSwHy6JhRKX0y`&r;)><6 zo|=20SWDefgt0!>zBkebB+L3Rtv#%UMLwMyH*eDTJKt_bt`F|r%L5LAJThcjz&tXN zyw;jecum7e))+9bC#j1!jPB$3nqqRXQp{!)<&84jSQD@rx3(ow5D)}aZZrP!k%6!M z%o>F>i8=5~eb2!!f6BMxv;8h=`VSmAsm84I_7cyPuxjTp$7tRJ9 zn_-LghRgk>f{e-mXZ!ToE#wC%e=Z_ElK0iWrCe^g0Qn8q{qYHf{b7`062lhQVP?8< z!`uVA%aB;#BoFypfnz@=Ng5{Np7evdJN%J(0LY<+`r3<`TKcIj2s5CT+i3WASZJy{ z+j|7IL$!D3`lYeLFFY$FxrIFRq1UH-zAOU;s!pErWf8l1_6vgL+{xnaH;KT0*Kps+ zfD0QLSssGqTfZF(ji6R>b)#?kx}}AE*G`{&rrp+ygeurgO-CDR`lHN@_*^bGb6sur zvzUb_3t;jfA@jFse%Diy#@~0DtvmsZ$F(OAJ`7SSMuP?rFGaX83Uw54t;AUylGpQv z^cY@t^IkY z%DM>)5I`b+jY(Gqcfo4t$+2AuSCPS^_9Tf->dK-drhAEe8!OnNtIWFQPr)!S8VHq!24a$sw*=G#Ot5);7*lC=`7+Z2 zy*bc!Mdy(ogLgDpV#ptc`Taz9#YCW(xYFXHqz{&ODi(joX-jsY`G`m~%{Owq5-Dww z(^fMV5o)k;59@lY3{`htA@4h?bssY)IQKNU-UnDqW|R9hxv}=joHTJY_2!e&XN(|H z#EHCKS1k?#p)Vzfz>-I6rYt4k^Qily07e1%-L7@KKp7Q5TF^&Q5{tmwsu)3-3!fg` zJtZVCdRB-ATODMEaQ(^6o@xE@g44L&KkAg(>-}c#(R`K$1DlneY74Fv-jM}gEPK|p9{g#IkZwUtAN8Tsp~ z_m4OmCmN;*nw}@4bR1K8D`eaaOV*f zZQ+(l_^shttV!jdeLtu`w6Pnip`*s(JG0pySW3a=TLdmuQ)gZP2s;wFvYReMt1eev zr4^Q~QNH2zU7##Q;#JY7mG~aHQNnV{~h1iGFls z05n+B{-j)*V)%N)rNlHb3H%xe754%nfUZ4O7*KvEcooA*n z1d|2-*^ev`Q)*9__qUkzs*HUo_K!SvV-=_)J}xn~Oc@*&YJ5Oy5TE>2fhgG73AASt zlzHZ6(c}Re*+K7|=x<8lppTkX?GhvpesDri`alkvDFZ z7fxAFCt_>AtF6%br2Lu{A%>nQyiSq^8$4&J8q61^Y4Uz(m0j@ZA#t0Q<#_hpBxD4o zh_RwimSh=5E>m@aVJ{O+EK3x zi&p$PR3&5zz5Q3{W+pY~PuIo>Bia872yjMi=;o3zO3o;B9a2Z1zqkUahbMMGsw6^U z{DUCGMAb~Uk^|iDBTQnqpj7r71+y?`6U?|UlMaMY!|w*Oi3(=tmGfd%fdOj}W`{|C zk<(La3Mvld;h(o-p-AcY_(F($44(4gpO`_g?;1F8AVgW+b8yj?1bB#ilY*9!Cz6Yn zoN?(Tq?jAhazMd5XAeOV-W0L*B^0%xs@bQYElgL1X*j47-ERfMKG=TtnMR!GuooN= z!+;x0I!VK|aoGNB6=cMc_>y8@7LW0WhM31_`nwoKu&`GbYTs{={Eky;MH;fnZid`x*RW!uyplEzJI`q`OpVz`OxSsASzeWaq+3yAxRsTa ziPpO7(k+6WfAojq^NHVxeyGU5opPw2hOLkCt~5g+cJ7IFkxIrfL?&5)rprQ|Xh@w&Ee#C(b|O)IY}ZpHAOGd_#p;h9`Th8q5hdGv3_O>P)n&9F4c zvSg2YGwe#N4HFF;iZq!pEFGI#%=T56phh_LnoA67nd&SIhO>;T2S&b5r#a+&AWlxq z<=$A@3izGZ4*o5eRSiSk4Ez)fcd_4#qX=Y#{q0pb_r1Z4C z&sxVJYt6W|p`G+}fqz@+qUjkljCnaJYe>OTgH_QVx-PF?tILq@cbiztUPOsH}a-s)7NVyC2qHDgV)-{snzKd<+7K?+nYF*y*{j0yM41`uvz7!X5$9;sYV-p zAA=##6h5p`i3Xll**EaT3o1&vD*BAAKrVSD1rRuBE#h`qyxbcjv?m!nl)jmj`)O(5 zZqYkhZ=#_==}C^}{do0DW~V>jmn6cuH{aA^Tnh4#_Ev%$g7%fH#@c+)D--zaGdg_~ zTEF`^DY*QrCyHNUhPdBR1`Zdn6Cy#MYMXsM^^g6{0D81dpBaw&Vx@2~ppU#^-v6RQ z>;#(t`sQ-=Mn0J5rc)oe$Kfuo;u9+-*$;FZ zF8Atcn1o<~_`X{rJ`R$tw}BTnma+)D7s7gD_v|TPdsORs&j zIVs$*!f#@Y=Y2ZauCAdr4upTztTX)}^5u|?vsHPdCtUQtm7*Ga^~QrV=7hX;^pIPo zE|`n+M80?%??|oP@f@&V)pw=m<4`4qloWU?=f{tq;e#!p({7Yp+EeB0c9pi!KL0+< zgq_W=)3_2D*1YqK15HQy_kjOv#|-?|9F=+?4WKlztf8<4H)$dC zly4SM0kOikyzcF_wL&`FK#pN5VZn^1&W zOislk6BQPp@ZL#cNXL4gVeL1n%AJ%)2X(uz7YMr%>xa>r(N&;ZT1A90u1qQvYv0AZ zw89t|%-Jt$lrj{Ti=zcMw8nxI5-6}~=a}3V_pe#d$y-V*GsS_v|3qo&t_-%R#K6ohUCH|$$Ng<}(>PDm>~q_$tK zpJhpY>mglGjYwtOoB?6KeAf?%egs&%Sd)i>=(ZUZL6ubXsGq~ONF+JA_GP^}t$bp| zn&!dF%}uUV8{X4h$9G7GE1YRP@+Lohtep2EsoZ7ByNMz!43gu(pNI_1I=On>XD#!^ zC}?(*-4!$U+#{^U8cFEOZL~H>;~Nz+XC$na_vosQ%9S3Fx9JA%WT>%bSs|QT#uBUO*;P9 z=ULF~1Tpf-C~&Z)gYn4(K8MOFlx#e>IV&72G0rKXJ^mo@C@?4Y<$b9aUA!QNP%avj znZq)6iqV8I*=I-hQ4B9^44LIMCZ|Xckx3T37j`L@$40tUL?#m%!ibE)i!1+d97LdC zIdyWiLL0BJSl3J)yf~ky;$6*`30M!C?>oK7~CG^*z8&QWfttU^YBSQmm%>gB9 zY2i7hjmC}(V|bg`VFvws_?VD*Ao3Y@Jfo^nsmbi)omO+y@n)?PmBJIq>3@p_vbtH^ zJjs8H7wgm-Y5<0e@YO7!tp@iSop3SwZ%xVaIrPAOict7JOD@*$D;z1U`{DOqpfck2 z^xMf%)`};>1P>{O3v=A+ z=4|3?MLjo7trt_~w2@Cx6aWdU?{g!j^?V}CmzU$aY+qS$t5+3rU&rxJ?N_tLXg<&f zi*$Dj#sstXv5u7BK#ViDH!qey21JJa`p7iPcRQwQdp$Lv=rm}S_t|_#0xWmG{LOaW zbXzu(p+MK_-#tp#r8D?+#v{gfKks-O!gCl=Q4pZGV(`>Rqt~KDd_at_Zhi+kHz`#p zuSlQ;xvyLISj?okv6*gOMLB#Dz zyieEQ&fBqZZ4OE)8lYmO8)IF7xt7}`a?+Z3%K<2lmy|L$tSYu5d$d{KegFpn^#Gww z@hL<*`AUr}eU{9&U@qoIQZdf4tX^|cUdiJ|Op*x!jr(wh-LaTrXM@-+=(EeLO_Q1^Mi zB}FPMt|VXQw9X)52kOX&5nXon46s6+mdnCiHsiz5R$yV^o17tH9{+`BCo9lQaV4qA zO#{4sr(-pFS8@m`4hau~oar%hTyU*@0V+(NOj`e1%A9WsBfqtqu#%ZJeLue3-julasCsP;OU&@qjRXi3q=0vBC}d zPAM>-N0abc?*`IWL@Wn)7A@PTnH3>E7;kP&w0G$m|B{)LfR&R$pAXvyz^_+yn)O~k zMYl{p*j#s$k^?rGEWtU#Wn&TZiW(}OI?w)(iBn^e+?vRXp7y~;R7A6skmP8^c8R1jO z6VdYV@jYzFR>RlXVV8i7mCdRlbCjKr|9(P^998n}8M%3mxBsq!H&URVNZ`KtAE953 zSwVnF1;7y@XCB~EixiWT6fL;XU}0b`i0J|Se(L}yReRhvwWNW-f`khPpSx05H{(KM zFo>oK(;2f_JN`KD1HHO=QO;9#PfnO51M`hg81MyAfHGchwDQz7%yyE3jy7Hil@#{F zMP9rk5xJh7+NQUVPLW(+3H_`4Ppl%7p#kGodpjBn#t73w;l*_i4$@_qZ&B)@!EM*! z0m{)h)a72~i$StO8xpo|63X&+Z{&WJ3h?5kJpW2Im?XHA*R>(Nt#xFACt`qtPCGB_ zV`8<8McD)oRD9I_Q4ynrU0mvHpYULDT?C4t3Tw>qfqNPCG2r!qC!98d)IFd@;SdD4}GFy#5>Pq2IcvM`QzK??ci4NNb z_lqM`lGXrpvn9bxkzy+&F_CVSBeUCHkSu?zsg+318w(>zjpP*sCj@g85d^zZ#&p8dAxkFLLH+? zmU*Z6=RfoTw{8&aq=hGJBAMxwa8AQReH3$|p@CDegY@%$QfN8WKHS!4AgF%eyWt8r z#eLm(dZUD*u+1{tHI>MDgzN=XTPl~sh{USzVKj>QC8Oi!recW1>V8C-;s96kLyY-t zClnExmFOGTRBhdJDch^#8f=cVIQceU1sY95?;*kFeG$T>)oOOg7I>P0y6OLv%sjzA z6@}*hMV28wIHpnF&y|$$FDf)<+@(iHvr>ypU0r?l<_94$Rh_&)oU4+)gQR_k%5@|b zH9b>GA0IUvfud!h3uO_lsIKppJ=U2)(a*V&#{X5Q_^};((=L?8QBJ|SL{D`g0LMXV zOLUfzc{oQ8510pS&8V~oPeKWIx@TD}U|ONDsx}rFrOxVif^~b*V7CFkYOUiCun`QI z9VpDNB8S9GIUsrX!^>_0-yzv#Clv!!!@yLUc zbO3x0I0pK@xWrgBS2gDT-0NSm4Uf^FrQ^a}FXQ;;ydLJValbU$7l$J?uepj}NqQnB zmx;?~ioS^?8~lJgEMz*-Vw~%Ckd4f>mU#an6p-3}*(Du_4Em|}_Q{+ImZS7a#L_?= zJ8p$HbI!$1QW{};KPEZt|Nfd|`tfff0oTAoG+gnY(@b>meyub~()HjTr!2_G!-ObJo7vKF zY~Z;a2$xsX$*S?aGzrsj_8Mx(?$e0qe-*@@VgO~;D?i5Si_-em_*v@oe{OKF28H7y z>O+iXtWVgd$y51nVs=C;>oz1eHQ9oei&KRSTECBuNd>N%bzt<_H5Ta(9w(uF$dPRd z2{stN7)tAFwSjAUL|H7eg*_|EDY#55Lm(EFcOf6?0K{crK~z{q%|3CR!!+l23ci1K zH};Cr`CKdwjG79(JVt7q6m=4fJzd)2*0l@dn4eoKby{_#VhE_b&l^h_U(VS3YkId% zqGUh?lhXg>U=t?tAVuIML-X~yWF=}R$Xx-FTN_UUN+>5(Wa1y-DhsmLuga+r_Znp1 zOMcMmcI%|~eP8qSk5Uxfa$n=Rt9bzDsjFBQHh`k@?eXj@{D4}H?Znm+nFoi5L?l@sD>VTB)B zOwB)j-8Q0(k2jlC%(edyuIdsVBNN(_X6To3QgZX`dou}6r@ZKX^t-p{yzkRnu%NOd zhuZ&G(*2}w<>94A^a)Zsn_nf#o{|o@kMtL!`!nUKe$jgP)gCG_S*`IsJ&&l^{O2(4 zc!p&bL_7oWhXN^0HiQ^vb2>B3f7GBOC&WANE2^#`qIG?CCAuETOwRUUvBAGXosVB( zo~FV{iB(8slIX3tt4fz!(vS=oFJ@vq8h~*y59krSKd?NiEuNHJZBlS)9s(c`$^D`r zzjkU)HK}k3Zn6fdO^x-Y_8_^U*1YW~QBZnh4f5k$zm(Nhh3J9tFa7+0E+hbPFA>%0 zRLzgl&b9sBjB@DMd04AG1z3hnM-G}`siCwn8Bb?L3WaHM)zvX8HLTA*B!9{k-j&4c zY$gbQ5x;xetoYzZtvxmy6RxVnI#gr=WLVc>(P*N>8m3n{*i4` zWLWVaou=L#!lv(gk4hcGvQ5p~I`HBCyN~`?uPd?z>HTNRoU}*(IuWo;9%GtMc<|`| zB-lh{%mHoXHxezrAA%JN_tmnej}kcUZ}1dXsn@Eq7UbG~a#E4EcYvPayiwL2wo%m;uAzw_ki+bjNWQ{H07- z52SJBypZn@Z@5TtTg^|rlcfxz=Tw?_87i4JlU0pW%lo`)Z^Q3!v_byp&{~|WLZ}cN zY29v?EO$yFy%1L9AzR&hDe6JUSQbJ(NHCHDUOlx(b(Ke_!pUnR#yU{Xi&zARB--jB zp3%aj?qa9yb%%7J%Ay1Nn)rF`)P9YwZmyJA^2d&Lav;|A6k16n$Y=2=QxP-%SXThX zyRD4hdE03Mk-v%!A#NBnXj~YxuC6yp0D|EMvv3aqcE#0^8lt+qR{Dwb(3>hLvRLqQ zO!TA2q#a?suL9;L*sTv}OEzumKr<@0bYUvCk${^!ydae8J5~Tzagr5)NCcF}3qROt zrnN}N8@rT~GH{@R z_t&k@)Nm3nt!CWJCxusJ#>AFl*Z=ks;#7O4?gVpcer*GaL~h9Ihs)y|>gHV5oKI_! z-Md3%syz~Wg|wRXYgfs#dfEh47!F7wr{-t8c~gT90(2%eTGV^1a6Z07s75l#@Hq{( zj5A{cqH%96y&-I8?crxb`8sQQ4iCV%Aip?-Ln}fdVW`;T*jW;rMoD*Y*R*?KyXH+h zwrJ@;NvXfA+W%K&3%jNH$$p1wKa;O%^BBK2y+)5+JO+0i_A=LBs{YeLXE z4=#fkF)4J_!WE7`nKn+6G^OeiJQa-dj~`U*3|KT*(3BMM7>$7b`( z(`DXuu>0SBFTvxfqlNhUqb5#RLyj*}kyDWL-4B-uE>{P6=g1R!P2EMyKr@ zbIwJ$v&Jykd0eGyGNTHoGtw-~?m$O6a!WP&gwp262@Rjx~*Y24OC6t2TFA+23(TeDnPu2N)BOSIN ziTM*@(Z}D04$bgSJh;Q9V|jN%>(@e?Qteafs)}D01#t3w3A-Dl^N=WmevG1+PSo*( z^xB_Pu8@3tBOnoqpq_fJ`@zWN--ZVHbIXltGzf;*#oTR>ZkSQ8pc1ogSCw9l_Xls* z*nET&Ej5hTT%ubUEg_X*S5OtBGG%@YG=AF0zfCCs&+%NL{U7~*V_Z>}6h*kjRI$AO zv@#+KOSgOv_3uy;U@n#Q#t2m#l1$DELeqhmi+Yms240n@3Q*(IMH5aoB(Q-+i!^NY+2R2&q~ zurX>9Z%}%Y@KWfSRbOUb$zg z`|cHZmZbB0ElQF1Yve! zYf3xmMyb0-F2tz!&J=UAv+nOGSbnJu4Ud9*XQl*eG=Ik_yYa@C*HaTD#!EcLM75Ze zZt9w3C2STRyu-h%b)O+TI;|Y%pf%E?0@~hF6dFoR!tJebLQ11;g zd4aLmwSqW##LoaQq`i-d)Q9CAq;yuU0;@3ccTd!WmruS1P0(K7KK1N7FE6k2JXn1* zu18aSXP}pb)_HpO^8}|O7aM6oypRt$z+{hXW3?1O>eqi55eV?DX4aYx@l13-Se&j? zxW{(G5qrdEdF-Y+wav+?sHz8>7UyD~P`2&;<;PZ%K`R>9?0!*amwKGGIKt4Oo$2F* zLB9W2WaKw@PkP4T!}6r^_Sm?zzfziefH`>Q>us6WLVZ)@9qotLzygu=&?7GeZWjN~ zml_rc1{V3L@0#ee-T+ty0qfUTQXSdiDG}EkdJ?%YS5v*F`m30k{hOE;qgVgeQ9F1! zE(Nu_U!Ly6C&Wt@*$E24Za7xMAU1%hBmfT}aaAniYJ;^p$k2r$P9?Sha7DrVZ5RKR z8E*zFzDovmITNEoju6H$&h^dv!XEZQb=Yb&i|={`t{qLs;CEI6gApiEsa_86AvKjl zrq9SYyiKZyRoNW`K>y}jBMB*Vrhhn|bw&MX?OKrGWGojJABLpk`?oINr=vc-+KH}4 z9J?pdhzFQf`gUc;a&CBJ$^p$`cev{qFXsNv{qgg|dSn@Gr_Zc}@88<0RLM_1#owo* z?N04`3VM2}y>wB)Eu)#IFB=^+8{YVD@yxS)Zz|KO`c{d@ zA^`#S?_jF{#@wA#fA$A2CkB-M+OrN?{QkBLgih-Fdz2gZEXaifV>wm@eS3ZB-j`yM zU#lF2ZuF{BJQF--VOd_QWn)FU@#aGl_&|#ZEAum1k`0Fe=&R}Zc*psKOb#>uzi^a< zJsQkyvy{!@d=~>~U#Mj?<8D^hJ+j&*3Qzni&=kdEX!VcB)-pi?ce zdZqquSm(6f{=a*I5XKAIXEqQ&xj&sSTcBNR`BBi zoTg%7ha;s#ujc!624>iTC=#9l>?2J7?jN+sW+B>yF|Sd;56ghaq5->thp%aIt|#9g zN&l^$qt~Q++=uS+8ht-ogO|M;#VvZMP2+<$9AU5XGQLW_Kh|>mXNkb2VfD+pF;XKz zfOYizcPUX%_NKBD5|SfHPfo)a-zZ|>s2k;XmtIc5(TLYYa&JKwOw=!(2=8PIF@)2G zYv4WHVFdObk3xV+asWg*X;Rffb z=xJ|%V^Ek6Tw%OiU|!`XYR&y}!uAQA?o-4a1LN~#pHIWWYgI9H>|Y#IgW?L8MA+>Z zn(!TJbO`L$8ycoG|vMUW@HRT6fn?W_Ci@1gKY?*i}Aw} zXh#F7v2(mvyluIaA`>9b>Vx+UmtiEt9X0>9G{EI8_n_2q54Ns?T+vD!n{pw42ub7PzaX}Oh>pPI&MpCXU%(-cH z9`@*aGMM@J&Z#)s?IVm|P^E{}Z-WQD*WM0xIfYB_P>Ur_=Iec04t?VvpU=5VM2|f% ze^;`2Ns4uIA^!~$?zhV1(|@avI;gPBLXbsE{)WG*WfRs#O|*9V!0(Wz&Y(F6iHxob ztfISD+L|F1e%nEF+UJq5Jl~FaL3ez9XzphE%zibbV`Pk;OH@`3sEsCTG}x6XZLi% zTO0G4)8#j@6-f!r8yQ47zTf+3-Cw#ICXa>k;ELw|N!rrA6XY{?w_=!>hyct48bI{U9n4}SfL#-L%8>eY54uIZ@ntKKT z`$}ZqM;EKiP$*nhP85}H^9qN0gaD$=G3rr-(j$yI!+q?85EgTe10Amw`oxY?0dE@68!=H|VXkNuozN2`kgWyNu zg~JpEjj?$}VJ6|1*-$(*gj8PNVQYSS@flVYmaPVg4$VJ0C?cf>&238MzvZa^+Rbi{ zia@RF8x%M9G8Pa~jP@YqGDVr+(?Kw#f&rw#QY%O%uJC*3 zH=5d}EkI~pm6*<1j@<>!!e}-)4%utI*Wq!1XiPVb4NF2@g5w>^rmjYo>lJ`^LEd}2 zjW>hY{%IqbD%U|m+DgAO(o&f}etKPpk@gP`neL04xImoGY_g_So#u3mTyPiliZx2V ze(Nqv*Ny)WfpBB7ebPujXPIh=J!FoOiC+cX>69h>E|Lu=>ptZksL z60ztiEj0_rcIz-E{^f+s(v0@fAvBpbeB*qIKK%8hI-hbPby9b~?X)_nWT>Qx!G1gz ztVlFyQwgr3F8k}tA`yQVrC8NI{O*6qj~OHc1l2g&j%{7EYCiBA3Dd2qlzRnMe=IKY zU>xHKI;`H|CWXOp!yCoqvKkoVKDKH2Otx{VsB6><{C9Xx-xTdW94+uFyxHAIB~hpWNw_HZ1+|e`k<=R1L@c8=AZf8Kc%V zcwFIf=vEMHNyNDIx%s#g*GNn-)d4mu;jlCO|9Z7Z^nWC&$S>YB_Vi=MbMO$MWiVkX z9({J#7pl?PFc(Hv!6eLSiQzd3<lz<|ndn;aK;mqvIyj02Sfk8g1dt4#)y0ky1zh zMO7#8jV!IPULYWCmEG-#r}JdRN2rUwQoQy*GAfD=lyZQne=L(fLa414T@18Rkv zHe>^4C|^#Rw#`Btf_&z23G<yi6_3$1yT2YIIvuxafb9^1A&KCxxUNVpqs)Dh*@EIAmc0FHYA$pA zAf#{FFXTL?mzcUXv=zmTWm>ieD1<`9W_k~zzm;F%Iam`7v!y9Vo`MbNskVpz&YEFq zs?EvW{|#6vE)tc#E6l}5Wm-(ax@fmt>sBLx3AO22zgydSm!cnIz?PvW(5B?x@`>92 zcQA50EyN;C;@SXqIFSR}I_P<=6yLIn!o7^hYPUEE|Ae^$rxk59K|iS*HxW|6aW-vE2ShH|5devpwx$i>{v z(R`EZ+7D2Fb47NWZq6;v1DT}Dy_u?imGybMygI6CaoMjI>7KxAC9wWusXbYzCK5)a zdTCSquLO>l^&e~&=}-wu=uEyVECA*keYPKlOGIIAR2xo#|d~MP@uxIc2GeluKy)uGDq9;2% z-i#s%240k#0JvtL7V^{F^Cwc=+&8XDn!Ov71m4f<0@PF~nFOQZNai90ogxZHz;-x& zxjC(T<`9p&<={+oi*a(KZb+umSJ1nb7njl&(t1YqC^F6}Kah*C=rS;1Y#I9~LR+Ii zn*8+mizCgG3Y2Ji2~qN&wqCUogfE*K424T-pgbHANDP*&L{Vi|{QfSI-~j4lI{+I-bR0WgO>;$Hz+Y>cLLj*hEY}Fb4PF-4Ml}{zQ()p zT{N)ANcE_@!ppt$9`kBd_A@u4sC4=?76k|i`NWvKddh0vwR5bm4EJeO-Yk&N3p$W@ zqQ^Yf$e)p9qP-H_kjB`>)tOVu{Ec=G^kanElX%xM&@&ilK4uFQ;{IL1`I4f3+FQ^o zIK_f9vnRF$Vc_IMqOL}= z>V?26YN*jyKFl4ko%#ng%Z`-^2!R8JYz6b4L$-OlUrbdMtEs^wq;!Om5J_()yGPF6 zzDmz^F@-=Le}!u`(rMLEL7`1C2L*lrl(bKadoixbm^Pg2BNi!C9W7K6tDZL617Y>8jGjZQCzuIVTMZubm{$>$+F>cO zQX8X4U3IW*f6ZwxuK7Kar30&T)DH8qoYXCYHuq57ev#-e_{qY1xqchdfU`P}%q&YO zO7QuyblW7GB(Nd9r=O462qN z$z6;iWP!6FNwte(ONRO+umQ*LyWb=CwYaP<7mjv%iIPA)I@xb!xRUm-0aCXu#=C-V zBG-y|R1jIv5}e(xx(r3ezGWdeRqk z`cVd0#Y*qY^*~$E4U46nY2#E|1R>5{Gk(bH07+p7)RRrEzQN*D8Zr`{OqQFxSYa*A76pZA^1d$A|&klZkvyN@A}H@h=JDR8+#!kQorN z)4OR50JFuIIW-mLgt%0hNlRNZ_!(@)2ql;ROzhZzg{BUdQK1tf!JTsap5YSituzi= z{V8$6mxpXd7|DK0y)9Z!!e)@;F<)86V?J`IL8eQV&tSH?UI)g$iHXTTI9pJyl(`8iBAjwKY{rV&G5Byp_2)A(1deW^6-@;_`R{KM|G3 zTt@3q@!B4+U*X=*nBSN5qf)a71B*ov4S`+RXYY^kBhkK;mM-~4&yFBXtR9A=NNX`| z+pQ-Vpq%ZDrl@s+Se_>ZK?OmYyWs$#W)VPfZKJ4mF#|lrI zcd01+`w;2VPn5nkg)*8qb>eo_)mFXiI2cVRWQp(=C=vy3vGX1*sPZu}ZNB}EY?dbW zo-rwg$axu7Ek_EO@YQfjJ@LvX5e?_4{9+X}6UBxN5P>bC!Hh;VE-AvXnsq{U!DAgF zYk(0NYqn4N<9A=7V%Yz_NH@dFLM?9ItB!_-{=UND*z%_hV3d4%-* z@$n1jOsQ}yzCowmJFO<~<)NIQ=efvo@VKN`vB%r&pd5L*Me^LfFMg8VFWBs*JQi_W zK|GdvdUCoC&T&W@XYQ7FXnnTeeUD~u5O`zbaQlj}m70xTw_%6p-zI!AY%ljv7?1U% zxf6yoRDzDFD`}Q5n9*&5G~1d4?D;I8&XKRr7hhdWRji_`&v4oGT0N1@tJ=#1BfS=L z;PrppmXeWDFbh*WHL1?_K_p*#k1N+OBM~xJd66Xjm(ITUl?7{jd4}eptAb<%+z2iG z9UimnR*X7rD)n+emfaXAp@2O$QF-eV=dO>`{*9f7CDhZ=F>zZhx9U-t?%)TGj8Ic@ z5&JhYXhx|qGR%Y@yZI_Ey9thV2{&;#vPK7#bRR<*=&d4pMwHl2S0=S-{cno=Qd|Z< z+KYVJWSwoCUNBFKka#IDYKeC=%{j(%gavfNd=Bsu?99Z#V|G{lnQ^viV1wr@N3Jg= zgV=9M++6SXe@dPs7h4(}R z-|b-(KJHJ%xW@Qeg>YJ@qJY#;HX5JO8R0iMMP=qj z2APp%EX|w#r)t(2%_16xpLx-h5O^?r9AqB65>{hjFGWuUx1`jNaEuTC)GHHItJ@@e zDDp1DcR5EARfPQh7Pd*2522+^J_^wk2DVtY7gX7h@u{H3Gjx3NtbERWhQjjGT3Y;K zLrt7CDpr?(!(S3Ldp~G4J0^|;;Q_XV4zsE2aB&JIqGYgNrdn=$d}`LCr@6e(DYV;n z6|AkTV0$Q3_y$Mw|(S*O*3o!OA~4=EJ^dC&c;te`>`YB+Oms*-$(8V`FRGZqyg46A03l zu6mE!&+C0*m#m_$*imo!3(TiEq(y6P+0>*qR@y3{@vg@+luO&b*K) zIy)k>y-E@sVIsqs?>MWBnXFlv`?omkdhBQZf6F;qKrU%_D2j`pbmb zgkAn<vrIP}g$cu^>7koMFbWi6f~Z|hh|-sk0* z2R)Ctf3G#y=?pr*tyzj;V)SX#}k2ziLg* z7GNx8dbwla;Eixhp?QlC4MRpeZl7%Y;BR4zwm-Y*8vDa!pF5{P7?f;tqzIT&Yy{{m z6j(Sxl<9Y2#7(&d5$1S5QbTXa6R^m+3d2XhQT8mSf4U@9G{hmp47b4!d)nmyGrO_t zk-()liMQ~x;3s&+f4SzCd>zYJBUkhB#+L|CnM!(%_~x_nJPCUw6%zvsi^L&2#7-(T zol&}0O{TS}(o~)6%yFR(s{>*g@56GG{(l6KpCoktn!KvDN!uuYl>d{;NQHtAp;4cW z|HmteUoRffx51*ApXSseuD0`g`h#q%I zr@k%jEmLT<{7REhwO0IAwq(xJL;-{0z#ov?CMY%5UIT;uj(LzKS*hL)Nk>2h%;p+>*(GZB#!eN!A zh5XCB^`mJ}H$o`zT2naTpc{O^yJ-{jWKNY%Do|am7#$SdTtScJNs!nhy|JBgY0T95*(hlxmG48u_~>?*}h$WU&db+o-6DVDh!2y`t-C)tcc zCOVEF5zaUvjC~UCi^Yr28r_SGpEYD^mBQhu85o5q?1)oSqPayO0PGk?AO!ywt-ro) zT0Tl9nl1}uiCyk&h~ZQt$+MX0t=wa8sb))9tdKuqUXmfAX$8(@uBSJb0^nsz%}rYu zIzDpjUXFgClYI-5&!&7fQclVao9Ot(H>!@a*CBo`ts@b z4X67iEaa4+2)<`%M7%`qp%Q9<9eeCVDO+9**wLxLC2*%$w(=ucUho!Lb`nY~yh-P^ zPr%xsQ8_G6A2@^Czf3Uj$LqdTDC4P0bzs~tq+AY$vWzsp?(N!N4*dm+phnPkE)6v) z@-UmqT0Fi#f(&@iZ#gn%xD|Q+1D0_ghmM&qWhl=Twn-WR`g;Ua?Inmw!htk9sjf=u zzPEJPoQ4Yrf^hy zRLW-xjPe{&_KB1TG-&rXam*qI+GkXCd&9wqhcG3}1N2^{w_Hy% z*Tk#L->sVnWR{8~Cu4cB_=YmA4oMutAHRs+);?)~=8PH)p1^+$!(%0cBnilj3tB74 z@-Agh&LXVvW2!v>Zn^Vyn7aE&-o!eeLbD)@KM*pv*S43V`Q6=GW+zsx+sh{|M}c)W zTi=aeu_zT-W&f&}7>~)|-!h$fQ==$qgt16=gR5BaLz+IvLz3FqJ_ixNqEe^U)YU1m=Ddsn zHy+PsYEBz~$}-b*!;L%EgmOydysj)_2Ix}blx13FaP7sNd4_xea3Iuj9Z_dkm9Foh zinkXk>7JTT+M^lE&55lPa{b0eGhNd-uD>;RI_6rr93z1^3QnH<@CI$>zk<$Bks2jE z=CB_^GG3g2VHtAGJry?>Bl~VPx9UOOi^>jT>}sR?8nfTE8tb9q+a%_8|zGtp`?8T@kDg8Ez;-h{?H_PO-P1W-oJaA^ZYmK)+ z5;mX(kHUmx`F}iH*dki>`%Tv5|BIJjvt{wL-?Y8C8D|-f;eOW^`m~Fj(Ln;y%rPcT4^jxNapf9sRA5N;_x3zPz0ea^7K`xB)rBf^ zY}OAJ`aap>viJUwpqYjBT6V z73cBc-vXn3RQ06Cj+nt;hoAp%c>2zsgWxL3eW^RE9yWVw@)`|-hEhx*`;ikKD*4Sih0$XdKf`HJa+w;+I{p12Bo^x|s+x@y zru!*9l!#okSsH~>?eE(&1U@+V)-=2IXb3`VuC7ey@3g1*kir}FO^Eoc5{!8!n=Dre zmz=B|)}1F2y$WE6P@u`KD(R=RVd7h{IS_h+1DA*qPtk_{PgQ@J0x2v?Vgat4xUerk z#*~~@5mjQ%dB1bUWP^T4r1Z?r1hzU+gQLt|s|6xm39qjQPe#G={HC2Xz1%ROJal zbt=u8KGsN$g^>ce4_cVhax)&Q1G(!wI#Li4pIvsh5_PsO&I@9nNxKd!p%Ln?(>s%P zMrLUcdBjpfu{@;J4}VjcI{3f&jGkKHoBdDnw&@?SCFJA-%yiLbpb|1YnZ`k<9ZM=9 zsS<^(>iE7Kib-#o<5@!A z{*Ac=#wu}xZbgDFV(a6f*^BB`7Xe=%A)rL#+7<)*D(A-q@6GvbH&ew@i=u>CPD^d`H<0+jcWo^cuMf)EtJ2wSgl_xTEE$aV*8`X->2up zW|A8z5|hT0qth%Tz-~!y|=?kxP^7 zAG=q9w20Fbp<(CL9Vb1U|>lm>xVlRlb?#>vZ7s!h0Nbmf`&O?o0tz(8o>t9 zL$L0!751piSazs*oY`sr#7gzhYrs`r&L&4U9fhv?(K;gP@zror5>=G4iTjrnQgBl& zDTzazoVppDCs`>D0OXl8bmGQnZI{Xvm1fh~ET+uvMi_`=!H>}_q7JyJU}Aze;?|y* zpB$Z=|Kmf8WC%0QTthm)8p?xkl068-Ac*kfa1Aofc*&m~^b%%F)1^#<^py;1{eR#dz?TjVqx%2lA0+N}oy)XHev~L$$ePxELlw zM%lFpwzNQ-z3VN3l%J=#^&dStLAG+kN3w>$nUDEw&SWxRYN9}KSTXtGFq1B}#wLKr z(f@@IG9m39SA%1|#_RP;0I{)L^MI7gLngK+#;Zd^D zx0qjPpnS@LyKrF)l1B&KQYIJ~@c><<(baodFkdrlFL>Du$opUt7iF%RB+^HI_sNxSG-LA4ldEfprU#J zG$l2BUs9djO1wN`4`E5k+_yE`;?_zrm8coZZ9E=ujJMGnURh7=V#;#czpWa{6Q_OG z)h4OY9;X@YWs{-hGuROSUECxqY{4%d#| zCOc9q*CJWqLuN=H{&&Ax6WcmDsdnm)A`D-|wK-r-F-m*;aiW!7W@NSCr-T?azIxsf zBV>w@f_6cJiN{=olhdV=yO_%AMWI|^paR4cZuKz(;=uCv(9`VNi0JNEVFflG*ldqo zS?8HvxKmk{S+TYemI zx9K7${5B1{@HI|vW?p?EuQ!+qwhb0G@Xy{qLLM(v|u@ru#!auotC^CyE*r5^p^f_5|Hrha4I-TLLDWUT}LE#gDBy zMH9;?%*W_HDGahgCqIU?+O+pUYbtL|50p$Ij6K&=w2iyrv%mGZW?Z06c78*8+Q6e~pNl}YEFykeB=x-zq#@mxBmr$0P%vD2i#@#ED@ulK zSD;e>(8oypf9{)}CMkBJE&DHyp>E}L3>N|F^L zc9{%{zA19p)2F{~NC`)I>H(nTETrm(qOJA-p~a57Dbm2jHA4q=P7 z{fUjt(4*E{*uaay0~;e{O$CWCwr+BUD5{BH41I<}O~~P8DPBQ#Wpo~VhDxwpy8*Zi zLob5M{elc7YnLumsL){l!Z>U&N*fXx>>0k7sT6fThCH*jvM zu}a%20^Ot1S`#MwB$G%q(xu2mi&o=XS*0qASF<_#ptjn~Zb*PBG~zKfe4Yw>i!?xq1Ioj?FK5G| zO__`#5IzfjVGh6wJ3UD^iFn7FvYZHm#-;Bajx|0y%tIsKy5vFzqpw`x5NqtanCCT- zMH+$T@UNr-o*-5>y%Ub%9rI7g@6@X^9S3t5Gx|AyWOz!h-pDX{yzOJ}j?`OA9 z`aTKwT8JnsGp*WitUlqIPXavdFsxwjBpWof8X77D9;ZM);&IPcz!MwaB#rK-K0RR` zzxl+9A?y`!t^s1J37M3NXE3r9ZPu4a;TwJTsjRh^DcxOPI{7i=Fm((qw8H-o?%HAzK6hdum~W(Fjz`K*M}A3Assci|9@TpiC8hX9J0D?Qq6xbT1+2R z-+@0LNZA^Ct04`ZV0d6cjb-=B{~W}A8mH``;JQ_?n7bo9iZ&&udJ1WTV3w+*wAnP%FUig6#~K@e!{mhHXE6 zzd1WQPY^&7GkqN~@joEhcmVgT>6pJBRwk@Yt8Za_8$NG7R~@}~0`Ds$R%N5BAeOeE zooGNTAs_$3Z)dLM?XyrJ)b+g5;5NyCGC<%6tZMsB)!CZo2Y(YRI1umjFlKv&M$Or?~QyNGDv zXUCbZJxHxzXsN3`cO|uNkNhdQkwe3N>#BleY&!EeV=bmE@rp+6HO4LqKi*g~!rUV8 znSm2Y%$~9oGxTj|P4T5d|!uSR)otu?zMVTP%s)*kg;vp4jq7ja}@<#NJzsBBJ!JNbj(X?S236J9Fo~ z``*57u;l@F;O)J4?vyiU>iO=OGe$#(HemGlBiiV}wzTChye$=9&6HGZ86q8|lOT@r z3du1;c%#niBpqQgVZ65(4ZBtWCz}TY;9eu)+5wL3T6xMg=&#g}jGK7Hkc=Pd$g)Uq zR&Zvu>fU*H@ar}q7pi3Qv?-1WMu9sdrmGq1Z(;u9^Vd=Ad#9Y~y zx!4G9(>APk^*G>hz~jJ<#DN_be9>Cw!U=XzbY8%zE)>3TuZvp>MH&V|Ic}(J5sw9J zGHkn)QH!Doc2+W>y+ADz1fUjy8h|Y*GFv|Y=yc7>lmaY{b0ATtm0>#vgj(I+f^^j& z95SjW0Hc!<)Hop!M^m)}M3@D3^G%U*vWjddhjpZz=|>^Gcx=_F!>K*dJz=*7+nVCK zW=KBNo3mnY`Vpfb9F-^>(cC5xbY(D=(Sv+j_CQ*;+PM;8RRu~4ECKI$%5+Iww50a! zWeCMl%LN~NAsb)$Ncxqc-msvcfLjE0rbO_Tb!iD*m@nR!&`nW4~b_d2la@-fghtj6YBkcN+**8N|W% zw66Q@E=B+S2|$=*ZN-v;*VbuPC@V>eq1_rinF^X#&~D1f?jmK_f7BIQvf=<3(y;}z zeHcv^3Tp7D9UH`@jTyFe+9L{H)Z@uS*EAyHPc!sz!AK0%?X*s<2N~80j@i1&GGG|S z6KoelD}g~~6bvU)u|=yUgSNL$BDNFw<7K&}9MGPPHpD)SAEf-s@+wJ5Nz(+WNulG~ zmL~7TYm@`*qR|jK$=_KsXUXi@vtdvl9|bT5g>%0yUZR>UJoPBl=^TP+2GEa^ipmQ4 z&)feA4dy+29Oy6(TyW6^l8}%9t!S)6(8fdCH9z>2$9@-W|28ypo7Z@?!EYYP{Dr284rXa+@^aa4C2Ea=dBwd*MkT^_su~b}|h$S4tCquDOHlt)Pw>;e_dSuJaSUM`zg zuaRBZMWYh zWiSHIW1_>3Q3%YjiNfqrM<3NNXQZKh9hF5*)JhoD|NP6({o%}b;D7XPJPx!w2M#~% za6KBD_FOo2L}OjbqtMl%L9)FPP+7ZnjVxKZL_YlBL$hU@jTMs}?uGI)@HpUcV29^`U5nc*hb%o{rjr2S9S?j_j&qhUfC^8?i5t*O;;%hd zvZl_E^)J6GRcrDj9$SEtA%!X)g0L0Pn%ERwE$d;gW7N^(An<~9G~k-ISt@aKGmqs> z!FH3S|NTL_o`{835`^mRS`%c7RU5(QR2Uwv#bUV}fi@>$VE{wCnWOhGX(Fz5)Qs_O z3A3K22K-OFX-XrBKu{UlNlpJ8$vAMNto!tTl2TY9aR5S6sbK;-e5l6+P`qy4MyZ9? zLv$3{NoA!}7H*bW7`x-HC(R?2G0vA&)GD^GL9?c$TFMqImc$`_73fRbdpFs1z^=0K z>sbr~RSUgojwJ8q`v=9mR8-IXQ5H`aDo@EomqSw~x}@l9W$v@ibhRA3YBH0WzxG(Z@KcyD*(W_w+k7v zI8t#gCioICN@{A9A-L6$V}ucIO`5tGw519JwLad}<3PJ|zygbG&k78hiB#J=pSh;w z2cnZsI#~`paE#7ZHCRW+U>)iHkj7|VeDS4x@WBUCT8z^lxfTgOX}4>6yu>{YcpUIJ zKn`#^)iDk0s2Kdr_=c;#>3AOPc;JhR$F*QOh4KpQLrKkymxQDDlm7dRkPYvCDtX^d zlhSpC63r8WFlkU*qzb3W>~`E($=YueCK6O74CU5LnITm;J~#tfEj%S=VJ}4gb5Ds zgXbyp6zoUv2+f9`L5nml4S}g(+Y3*$NrVW!dG(0PLisX zn~(^$U1cUo_7VF@&S}R%djtkyDSf*#<03I66Fj%v*`(Om-HuP>Exk32^P0*UWUt@x zr3Nshkvx{0VYr3J2Vg51Kv>n9^%BaVH!FnSBN^Q;*WX4Ii{r3=GmUC1z=&xG6LDJ#?lo61zWp^^-qm~!fSgc`^ zQ6H(0vX;I7m1G|TTQYI5BLgGDgZ_Ade5REhz$YfU|9-A+PBJMw<#$N(fN6^hWe8 zOU56ER;_0Z**Skmv9gy98x+;~l4al?2TDoa3c2gyM`Q%F(Z=k%r*unClH|l>Eg21d z(`nZ;xBkEW_Tl$&-!La9vfEql*-H7YvG)xkcpk0dfDS6I7;L8jZrd)#F)zGx)s2Gf zXxADRHi9rt@wO6kOi5vp{JdnneDUSi(yh-BIpDD4U@IyK2G-HKR0{*`jUXQ8orrZ! zG*gbjowXopEx(fSk!82tcL#8ac{9Y(wv&!cm}3K{9R(_1Ya1P$0xicobJ9^rBHp9N zfez&WdLQPg=0N}KY^+@~(j^Y#4NpGeSmTbv_B?ym9Q0(gab1lay{N~J=ErX$v$7jyXc5T+aLJane${xTOYuQ~^~(yZ~1CuMTD7;yj9WJRp?o@z=O; zpDv^OAPdC9{z?AS`655g6*&+FUn$k?+Fyn|bgfk9Z`MV6Tq??xjV(LaR)x%gBH+ga zxFAGY1RykDc+*em;(ZGpg+&VVOiTGsUXzFL)yX)dUYIP)rCV(HKu5U-lf0Ik@9;=c znRWt%j%*n2DVViDaz+k?N38f@TV?12*GloaTvfmKr6)>qHyi_=(?tctfPVgCRF(&-EBc65R@z-!KhAzzd$Ct14u?nKF&aIjS7mP!qzu{3NoOYD*&{kov7v7LZV zv{A%#$>VAcKIEjP&$2=Vo;D1~5WH7^T-wVi$b!5Bd&}7_Sn$Ri2{o)BZLANaGbA`! z#UGv4ReKl2WYyp=sWlYyl{rq9I~I#`SIexQK9h6KIbC}8=qg2dYvt>2K9-fsS4gkk z{bbi&N6Wy0LnIDI#M2-*>z$2&{9+qpb7Z>=d@1LaMrOwT%EdDu8akuh6`-hL2=c$> zuAcoTtP7+Q1f(_y+X$fu$2Q+sIReQ9msQ(S;AIQ$n&L$ZqK5>*DDC$NPC2-o<{idA zct>N)2{l?czEhBi;kH3%{Ex*K=z`5fvS7g+nLXo2NlZ$R{)30gz+t<~1-qOlGv_Xp z4?lig#*IH!;u16PJpiiypn&7G<%L@)A{D`O`N{3j8#9iA8dfq|E*C+hm;ZRLU&=d_ zqUJIKoz8&b{)%4%?V=*oPkg+YSF|`t%9e#&FJdw7rleqw4RP9kQC`*w?Q76RhB;j? zB%t@`aiD`ZKrJjkRARd)@+LsYhRX4nZIs)WIaXDamutIaySL*FfnUC>#Co=lYc@P< z&t@PtcF%bn@HpUcpaVIe^DEXBJXP?=pMH{j$YMtweY6>~xL$687CWvr5oC*AVU@R? zyHKvWo~a#@fJJW1#?4YQbFst@?{73ec=}P-RPlAkFR+%z>;hg<-Y%<_@;OVT^4Wh& z+S+2Af)uMu?q~?KSvXjzX7NV(`Id)d@YNSc%&>mYOhD!KOqY0QW#CXAOhEXfSLB0J zkp$q4tlg+5)x=`5;<3Q>{PIbJf%zd_8gz}v;@mjIVS@qfJVC{WtwjMT1!3ur@G+S5 z(tu#p7S_t#hn|z6J+GIzfxSQv;%6s8^8&_NDcoz*!*-e60;$86owzi!P>jMPt6$kE z{1K4A;&4kGi%T~0G$nO>Kw4aOvY0sa+Bnt?f-sb-WaBPY(Iw|KUc?|Q1$By=3{Jo+ z9(#3Cp*^zl?XM*R8U*n}`vKU%+nh8>#ZMg*g{v$mz~N67lG+2{2V683)IRVvhN}t) zk}*7*n~MZWFsKVIMf4qbO2X=}IH_l*%$()Hf7X*4IL0l(o9GIlSFk%HsaTL>FG4#67o3F+YNR>NFfYzzb$$ zz0J$^``enqjbzc8t9>$lECpLAi(z=zVMSn$jXFyu5q8$)uRl-2HnWv-#ieIU3N-3q z8C#@Bf7x}|0G%9uUA$BlELmY1qw}BFQkUnh?*e4D37{ zD_COodHhgt<)@AztbR5Cz9kiC%^H?Ie0J)ckF9VvmMs^rTwK%HceaMq(+1>w_Ok{q zoFBtB2pHCb-8JA^7)c{Aq6y!cSb@BP@3`61xW-aL?cf#1Hr3Q(0WtQ_%2Pe+vY@0y zii-;&%x{*WqCzPs$d%2T3$)wrGHke!G`KKUfLC`eHI3>q(oWlaxlfxwgJJPvdg z2WqfIn)8$aL$Tbz+RwI0wo!sAH2{hyO`0VCdh;y>VvNl-XDl@xMf-uSPqBbb2Jnb3 zfN_g`-3icH1@!XuIN)($hvR@5yJ}_OqD7LQpD#xoak$FKcHP<;4B7s{mnOJM`x{+x zB2Dr9RkHHBze?$V92s!Jp^|dQ0kES1)^MV57EdZ$aNfYlLHVy^YsdTFNN<1|NwD^6 zKm|@Ou<23-tpvzY6${qMuQ&Wn62|T+{mz&G!5G_NxUOOvb^^jgCJUF!ivN5rQ9u16 zS!MN-2)Vr!YxsD8GI4koY(wJqrf6ue_+U&mx)v|7NTU!%!8wj6>J%Zv0-!X&U<8Id zQ!8o|1fVF81n?pTuVXiq$(%p^O-4cBi>A#J9&tp+75wHhDVjA`CcpH$?ESmnO7`w} zUygme$TJaTQnEp_6T`%7#a{vvsUR<|0ympcW@rsbF>Ikg!VgW8SPPwvlMD<)3h@KD z5?x#=iHPb0%>;C8TpC7B1OSq;u0$5x{E!T}@H9yox4+>x6GL370s%AU$&^>#k`epu zC8?)PR4^nOMsO1=aY7Kl3@Sclkj8TSc$yH72v5ZJo@iS1Wu1ZNs&xR+B^ma6^q4Fd zQfB?IZhWi)oNl#w&N7*K@Z9{m@0JD6cJ@=`1B^S1ahMswx#2$d{ zZq)8^w_749FbrA!-3(d!<|mT9q7e0gZFC?b1MM}g5VmsAmdUValmiBe46wwC--aAH z%u=ZOJ`CAXs|iphp82NZoo&m^nS*>R><{sDqtcRmIpehBp`cZTuY<)9;-L13b`cws zh~`lvyX-MUcG-PrsY1K01Sp5KF0R!ua=2RN&Hh1tUN{eiK1(5pca@!Y-c7pq=qWvV z_LLNCfvCg&`#1oIF=!_QUI*AskA_ z6xCUlqa7-kX#L3v`_2Y!YIc9kq!wnSE~T&7?PHJ%0x94vzd50-&L`%40}T;gEh zJr>(2>+4{qnSDQ|Lh5nKSq#$9a-wnc?U^aZ9)GAzdgU8A`v3CFFX42e zxAFDABFLDi1&s(WeLrQgQ~>Z^x@1XEy6gj{eY5c!Acq(;H6 z(y~&FFBr!#Zm^BAJyU~)V`w!_$|L}}Vf{vlgSOYc`|g7e0@U7SE5<*5T!P~lW_}>p zYp*?J_uY2W{-Q4;a4b@~ygwcXJPvHP9H4gblqpkW&6+iG%rVCRP^NTj)~uaX=%~-X z`aERm+j>&CaMg7@qH=NW7CM-kSza_u*DiQ%cuNSEuQna}@jahu)V3Q8Yvlg>zmhp^ zrSvU>L0QO&ej$P@8mesD)xCe9Z-mKAoRbT-;#5L{r~Q|5AR1LkK1uA^6M?ox@{6SO=jBoZ!@+g<>cJC_ z>QR~TU^;o5Qp?(aA17bcz(9I_zFI1pzOrqUD~;x4CmJzMa%g1P{LY!U`OGaxEPDy+ zqXKbh+a&=sC4ol-T7n$tA(`ZN8F>a^DGn!CC9#ay))kAbW2L_=mCei6NIcLJYP%4R zVE0Q!8A%I)EZS@-1#x9~pjHUmTN-W>G>*xUW#PL@@Vg8NFb>;hA_z!EI;4|32upEO z1^}Qc9zYqkbY;P)vJYQ?>LE~9!MJYmstpnk&@2Vu2enXi96(w%5UR>hM{J8sn{=h1 zE>l^#>flKSe{FZTP?#%2*^9B&sS?}S();(2co=bwa_m2pug{a>H5(;%bGf7g97;x+ z>BwWYof2pXX&(cq#3Ql^IFUCr5*buK8mn#okdJJ$d5KXnf881wrNp*Al#_i*w}LgB z4FWoXt~)53Mk$cH++^5a>KZ2t;>zWWoBs%Yl3|_USa1A+r>Li>+ERJvwMXREoBklF z@c|~l*izZX*e~g#0pe7_NOR%l5?QrswW?5V%FR`v z#s|AW+$tFx7bi)u9}^Gbtns){PDqj@@Hi31KU0#EurLO{sXc@7NEg`L*2JG?i~N`s z^HdM!%@=Nta~xtFm@rn5WV@j=U~Y$xzA=AvxHL(+d>eSU-Z=!RkKqIJ3R(vl@BJ41T*%8_n4-6SLJWC91`=- z&t!(JOWwDc1Km6YW%Bov0j7Kiu&7?{y6dhclmEl-{~#-{&2FcmJIS!&!({TeldN`$osZCZqtw%=^1j(HP?Vg0L@i^Y)Jww52O4qoaLW=-d3)8dAT?;eU99C(+x6n z`YhAloMx?Ufn|1{*(S8*!;fwCIN)(0QaFHdcG62PNuR!bB?BkfhR#Pi7CCcOSd#IMYd40QH-EmY z2JkZO&_n$}qq(St3r*J_z`3#WVW|JsK67Qi<2jDehVSG&j%0@L3;{tkAMmaY!e>q4 z2B}%F-b@+@u3<2^Ct>mItAlVt>D?t$aA}M=#-xi^+*CtthNKDz)DX<;mK8`f(qn%3 zG_4|4HiG*g^+w?rg9Vdy6yRAgkj;>aSz=MB(YC@oT#i~GJ_zqT&X@u;9hF)r9GJFc zJZb+W6X!9f06C~$xIwDtuf@(B2m<(*j&~~dL92$q0l@`^r}l~8nPmKrt+^o`e9f#~hA)~}Nb^Y1I_#IuLaaE@q}!^Pf|GtM1aJaE4J-ukJrlRi2~WRhv|TFp zCKUsOf#6raehIv>NaCUg1&Bkrx`RJHq^9a)`p4+S<`%0pf za3!+}+rkR5^=e~wtZeF%ASa)9uC|BHD}$wvOID5&5Yz$YUVho{<-z;zlO8aDU5EN( zvo;|qYzLlH@YWZs6;sZ7fTVFy=kC@mL%MZMQ-Q$WPBA`lm!O%GVBZJhR4#y-HEUMM z($!03(*|fL83)bCaOcO8v=b5~iTYi!Hus z$1se;Z8>oOq?n$n(UaZeI=}-RYtTmx7>2q6h*DfyBDwjwvMFz)Y%VASKQ~Gqp7Z&P zZ9$NBB|AG4U`jT4nF9c!H@1p(lU)HQq@*S)pM325dLj-eKzPgw4`MO_p!|_mj++yIO^A-6jC~6*O`QhAd&E zx$AGZUI>;XCE>I$jD?FAFIHX{j)Z9-5l{eaK~6hrlpLoa5;(c?$}45;*s)SpRu&Z8 zy_JLQ-UmSSnP;Dtjp(0E^#g6^OrPutY4Au!`vHNTS6+J^ZL@V+G|czFF$c+H#=I;8sx(nv1B8M<+Xsu9D5>|#w5y^9EXm^r z!iXzDr(D#E34k)8iR0NF1@{4Vm^)hxTXj4ED>e$X!*T)Wz)D7Zrc^;`xe`(#Hjr)z z4WW+=NScs7>n??96Br~Rs9GzMYI+Rkc6eqS0*l5W6#N*6d=Cw7Lq*OtKp~ZRDo~ID zXwq!Tj+0z~FS{Kx76@6QE{{8dW*r)sv9dKl>(HHs$;9g}m2bZOP^wA`gj-Ga7`X>D zQm}0ZAVv)|Bw``B(T<`q~sE|_F*(fP4R_zQ5iRIipHP zAMro#Ns}(hK_eIDbX;sJsTl8kFyhUHIv4R2IH^s=uf|-=Qx#O7NjrXwt>xgo^Gtu+ z5}Bp~D7eu)kdJ~qdNs80Jpl+BgruQjPQWG(X)^buQIjRbyup8hEvhn)XKHZh@xP$g z2b)bkZ2d`qCRuuRf^-A0vU}oA5(h1!)Umz^NiKMV`0_r@@ujc6Q4;f8fCfN@vT$g7Sah#v$r2I+nbiA(%&(_7x&!f zo&!P`AsWsJjKzmKXL0OjqMb}k04Pqnj8Bj$ z$iXuxTl-+IL4nOf5FKIO(@VoRY1pKip|z2eTJCN0VS#M^4z2!I?$km(L%=#+cy}6P z;U=`Xdn|iVPHvwn2eMa~94q3^$t&0QBD3>G;7o6XhR;FE5dFysn-EYm4WOguQx z$cO-<10E}^gUq9%u|H17A3}9Zkb)=$P=Y=L3}Dv-z)nSFxo*Lz#*y!ON(L79JPCt8 zF7j&uNHP)^>zp8I%Y?uamo}2bo0$JYsPFXQ&VM#21r?lk0L&tZnia!no_~-9I3gxvrRml zADRv8fMwHu!g^t^Kv_IzIPenG;D1^43&!L+*>B(dkIT0T=UU~HuNrNHyaBYO+x`Nta8(@bQ&CAf^ zfX9LDoCBP%I4=_{Ua(++%$Yk^PCM;1os)G<=P$o|VDo!+=*^A+zJ#)rlZl;3Lf>`D zvt7=2O6FdsJ1hs-3Bq**3Pa)iBaczE4@28Pc?nUnxm%*-VXM&|#~&tfFmPQ7A(~q` zsI3vUm>pak0)oU#XyZldN-;@P!+uMg91KpLSHQF)@_h)zWhoy?&+rVv1oXk(mF<8ToVUzR}Kmb5vWw@JZI zi$qZu3P0w7tl6HGFU$iTW+F!Jnzf-{89|EhUX2nH)uXlv1T7~Z6JjaMv&Pjw>Z3@C zzJi~syYQX+y%P)!SHT@TVal|p1t|q}0dOhChiz$fvVPrq7-?M!Bg^ZtWu;Wo(o(UN zsfP?2GF%QmbiAbCyz<&w7|KRD>#=324hDG@adxs(VF+d4Hu|w?Q)~x;e-5uA?84?l z!`b}u|I5$6$fuuu3f|LLb}Y1b2+G(B0O7b@xL}dQLrdzWn{R4Z61H8oCyY_Fi9vz> znP;DA7_JpQOfpEz0MADrb)@XM=bqXZ46s7l96DWTllJ{#?3i`HGAV8B&pW7NA9CBy zAt*|KO?d;J5MT?cbIYfOthH~#ToKp|#$nzy^y0X|A&LN7d$_Bvx(cTg!A>5v`S6b; zG&Kn6*tQ5R(w5ODpME0Kf0{0jJ@Od9Zf+Z7b1?-c-+0e!MrMYbckX$JZQf}@-XD(x z9tS)Qv=Ik%yg^zRo^+Vun9Z$58#ir~GfqFR zW5g8%r;$PXC7K!*{6U;g!jaR7-O{87ej+;=p&WFtf|0P9v!-LQ`zbUa%3)8WA5J#v z13;#qbjik%;)w~mwZ{h|L@_Yv!&73ooyRVAbwP|pFc00Sp3YMZ#{ z9d5m&qumgFp^tXJ1VgRZp!A`N4V`ldyfc0ZawsH)XHCW*nXy{z zZ1O50FqSJBedl^6Efg!%QJ87Wfj637Q2Q}}5i_75tAGo`@t)d26j~V#*IMwALJWZw zMSvj4QoydpX>V2h!S+v0Wev20DrJ4{2Aue_RDNBuKnn6Jq<5bz88vbr*b6y8ZSrtI zp+~?XM^aE*1KUmjT&R5%9UTu28vav*#6UU}owgB2LTz=hP3phMq+Q8meZx^uWqndx zMFCzn$+kSG54M2=4#4SqAx@J)*{M0hen{<^S6+QNBvjy$VVZXcqwO*f^6azEl2cAO zRfT3NAlqvo6!21Lp(Q3H0$2hlj5OH)nJ2eF`qW+#sws#D(+BA1-rmNzG?whDRs#ru zG?qZa0-Utv=Cf)HvEGpuvj|5YX*KlV)JMek0l2Ql)=Pq6^yk-3@|yQ_OoR89Hk+n` z3Sj^b6VK$&Hc0xA3VtR%0xdRerf<{bduC}ukw$LoW<29Zeptsiro8ksVHDuOtvhWS zo_>Z7riHOZHL(pi#w--eV2B$-$UCj#1VG0Sv& z#e!R3#7O_%InuAsv2wyO6R-%5!ZDy_Qdp2Dr8r3^cl~n7gV3H|n2&3&6yeBter4|l zqpv->_i~P)?jxxiXV2S%$m{jvwxc5gLBkXK9S%d;ZKb zOK7PGl*_a%)~Ut=kI81n;<4ZBH|5Kwjq7F2x>YdLyGhor$w#ztv>orV?oskOoU)CHP|KPcOr0T>`~6F|TlcCR>Jyq2pW{SE+uL)$p3IWuAwn= z{4?*!A3jmNo?|(;i5W1aEwo`YZKTHnunf#o|2YLRfLxs%d+c#IW_mx(o4jHaI@9Vw z9ooaE*dF=bJMUqxDp5K?vgbSwcpUIJ(54*VSV+)?aj6 z8pckHTsqEm#@MuDfiK?R-=>Y;OKf}KK$umyEmjk+Y>+m+vVY>o!8Fuy0Z3mCm(&&E zQQ!HnSF#zPOF_3}+2fc)6?mzGz^X!!i*{YKHcT=!iw=5*P~!j;B*};cM&P0drX^HC zn&g;8EK6L6(}v=p)e@H+C7CHOav0Tx0pNCqQ~;r`)`tafoaE#5o%QS1%7*ppWd59) z^5v%=NpW$tBqreq@gy8Qjgw_EGPBht3T>k#CZ$0T@Ij#Q;Y%`40gA^DHdFv!@q``< z9OP#V1W2>AW=#YfVHv^><{!wyOA`Y1r2!pKMxzkUF|oBe?1bn}Pyq$EM<6dVOS+z2 zwS$(uFz1Zg?0c> zk|G#Z24SkGcryg7GO47&;9Ep+73W%UhJKYa$FPIjarxCtIrcaw8S6p=kjvUgzIgV3jyB{}poILQr0~(g&2m23gKmGT=U(4P1+yg_=Go-c}?H6sF zphhCbqzMxz$ZfaXCPRh{(fkak0De08(UT;vfhOQ%k3FWgMmDV7paUq!D)b;duKc2l zE|NnIIYb5m)QN|-PHB0WJow;)^1+86D6Qw8cY$1T@g-UY_dLUXSAdhu#ve6ahu}Va zddr({{!16|EW_2;UL{keOp!UW<|ut^v$x!Gi#+k<9alqrimUF<)VH|g;;m+fv zIX}eXK!Pv7_yWEdlQ3zRwULt#p6S1Q`7+7fxB+AJt-3-c__iEJEVX-ZdQi3m**nom;@v|>Tgi4?;TG?MAslg3{!*=46b8V`TKH!Sw?tHQ#+9H$o%G{CMGivLnoQ7vVq z#n8^k1!w@RnxXt%>E(vM>)ido@q`SL@Gt zc8y_n@(qNsOfg$@cx*F;!HTMKl(P~7V3~p`#VixbN<+X!C_CTLuLM5|iG_s~2uGo? zUeZ!h^~9P~7?ossQgF?N(2)c|DGMN07Pfv-Lxlnw+ixrkr^aJejYGM0YZod^7Y7jT z{M!*=G}!=FEV|PUKsu7qI9fBh70h7{+J&+0gOX|y@rT+42(RD~yqUkYCr!`cA%$%= zn+SIlg0)znllM9fn8HO6<-xopopcn6`DLHL86ACfu?PBj534*1T*mWgbC${FRP`Bt z6vo+>gTBlkP(s{^^IGG}64e^JEn4QXO~Pak{n9 z3ef~Do_z8Nnf&c!4abG=V~;+jWucac<_)4QxZnbL`#*07G$4?b3i!B|mzB$_FTbkb z#G{WrDyN)$ineE-xc%sm&u4- zN61%SekD=q=(7OFcskMaY15UyapMkCAd2Z4trpY|j9PQ6-lk2PWadvZHQd_uYZZ*< zn9MT#@(WHE10Y4f#2S)aw`QI2hu2QO6wNyhV;$Qw`|N%9-6!|md!L>vXB#f`S5oHP_3tFpBFwzI?@s z6%P1dCwjPJPvpqXgLSm zpbCNdS=a(eKYk$Ksfli<^ReT>u=D24lSz{%NojGB#&N%Q$7}EVU@F@?w~kKVzfiYJ zyN)Kn%g^J0$APWofMOCzgbU;{{PJ-+Pf4!~*#s?+VTX;8m@cURUMR-ul2;ePdS_Rm zj`!Bmv9q-1Umpe2>`9)F)?v_3>VPm z8$coyE7Eb_TM1B_u<#E&@D4O!(yFYiR6&zlo~mK5q#U3{H3W$A@(L9$%8JWm9W+EL zAv9I<3RQaqgJ7!BRg)tcLK16=CpR%Kl1*BSaV@}I*w8vBNXf@OkWS7 zf1lC&zy?u{1|(QCb?Q|4+ut72X96h)T*l_N7=PK|*4Hat9jU6{u zcE(nt#H0jSxnh-k_0?BuU^*{1PtHH@eA$1${bfME0j7T;Wp3H}{EN?3yM|*B^C9nt z?>bz!EpaSbxNxB?S+YdSPNAQ`3hiGhaKlPr*}w+_Qj*Pq!m?+YvDk!RIKwXk7VzB& zXq)8c<;l#M2Art_fZeNiFWBGef!HQ}ra5@ z{6>2A>VYkYdGgb=pY%9z+WL9?9tS)QcpTUo z4q%+++`}~k$M;_rFV>HH=lu4!>fcn-JbL=o*IrXi&gY+d#y`h26{o%4Z4=;2L&2iG z*x{w?alqq1q;Y^UEY5S0Dqoargq@f2ep#|Iv0e^2ZKBjB!Hxj6DPRwUniW=Pw~K2U zV^li^?Yt;$4bQml53E<)a*xk!9Ql4Rj$ zKoWu+0Y^~Bf+qoA=Z;CC;6_jhXgVmp$kRX+8mK{3dZ-ZMl8`KAX3-yjZ?xVK&ZJ~O z8OHaA=L2%9(@BK@6(BpjjJCAtdkA=rT^v6<)ZhF8&IiXoO1K~R3gi*U7Y))x!e6~( z<%hWD=C?rUkOJfTe-0IVWf zwfyg!|0xi`tvR7i+bY6PLy7{cI#+uM4R6LahBISb==;W=@4ojQnS>*fX*id{Gr=Hj zz9A7NOqd`S{_aAhmy6q8y?V**x8DXp#n;rOhJCWf&mp}6`Imv zhaD!jQH>iyHuQOrMZJ9Z@rUy6yYHGj5$gHppM&PXAH@vqzjf18a%g*Dt2M-ynVa?dQ zWpsJ5JPvpq@Hn93zmDr1zw7l>qvE0>x%!%GbnGE0uVZc)R?Wq+)6YKptekk#Ns@w( z3mn_{M?d_83E$pc+XVR1-c0b)_c-8jAQCyCqa%c4*m!}QzsXl8>(XOoRb_$1_U1e}-A>~(OcQ#n82`1ml|3evl_I2 z_^9xTD-{R~SlKYia6Sv?E|i7y7Y3R}*uEq(p#K2o5|qTrr<@}1fAE27uJFY}&~iEW z;Dcq%m@xoio|Lt#)+*i_2m2~_-*Yzr6Jz6s*2K98e)N$?6|~^kaO0nDl+#W<-2rFN zyHMU*j3GPiw39sez+Z(&60?ldsJQQ*djUw)Lj%nxk39B>%$aFyt;8k7!4}J#a^#Um zsum5qEkOeEnYK~pFPIOo=N&&NV+s?dz%DP|5|rXrq;QTjQ9BM7oUX)epr)^Ji&YA? zA9Aa!djU_asJH(8mX@D&N`t`fz4xAK&oB*^i*{4`_wO&0Cr_3Gesh2<`*oR=Y%Y<% z{`IeN|NZxCd4gzRwSdqBXbW9>>7{b*AFqYom26X6@K-H7JQ?G{PT-&fTKv>g;{X6a z07*naRQAAhHBqE#+BqIcG@jKlX*xU;V4*e?w@RUy>Z#xh?rnRvZPp*3!93@2z~g|& zfi2(w$0TYma?GEL?L!IBX1*A9zUWWCaKH=4pasy3eEXeuWFo-rRE*j6&;(a!2Cglj zWb31N;LFzY&`ZJNfX9J0#ZsCA;`m(AHgX%O<}^R5a15OGmWo!S5;;LFZ%`M0A&q`0r#}1N zM-ItuO6-EK61J2Qux*XaMi++`w1a?s#pRdFF-IRGB_$>9s4OJLEsj}6V6Md&_Tq~# z>NX}5&=x)rZHvb+GtPeix^P{E<0wHAYMf9DgmsVvn<>;*nKpfztXjTGw-H@^ z)z$Lo-yfCRVIbEu$tbz^{(JQ}VLf76x0lpI9sC#h@y8#PC;j^NlUx3Di+-K7Kmi#> zdn;8qF8a(f&y?T){`asyRi;~U3UM+}dU`sxHkK=JVd>s~|NU|N`dED=c*FUDe4)@! zdV6;7DHr_icLtb2DCJoTI|=K8br{YWV1}9{;3c+C(O!#&MR?j}q1`8LsbX~29Jgcf zhBWQD*PgQ5h}{5?pbluii~->Iu4|XB+CKgPL%Ma=a2VS*$7NT(XHcX-WqLcd|V&@K>cdAYU| zDR8J`scGM)Etz)S8Zx}F9tS)QcpPwZfMb!)Q2@8O&2;6;m1;1z3R`{&{_9xpj^Nxc zSg=4IfYEh=Gz`bF3;#RadEiT@SAj<~U1M}4Otg$`8=Guwn;ScuZ0wC~+qP|68{0M} z8{0PC?DyXLKWEOFp6CE&Rdo?HybYdxDT3 z)=DXboFz?>UOq0h(r?^WVix|vrilS8AYV|jjLCp?$t!#EL?Xmul_cPO48msP3jB%h zk$3x;*v(Wpqt|n@Z42JlvkHhXrsVTM7%2MDdAerT?uS;Z zuu6H~y#(0AHB~j}gEQep!Ppk2MMO9spWg)Eb%);%g_^|-2ZFT9=5=9(Nh%x{%yE*~ zA;n9-99Hz+*LmTw7~%?tiH))9U_91wOTm7(i|ifmruB2Z3;GQsybRRF+s_iXzg@Iq zuI`MP^SA_3etOx7$`t^0xcsnYQ|s(--Eou?sf=o%x$77bMBGFznQoeCpt@IlWFax~R1NN~HXP=pmMV{Y!l(gs$6dLId{9`_a2Zy}g1!dVfm{3}e$j z^qdG<<+>wBIsh1%HUl8VEa8F$fpYclM?7H)Adcn?x;3w!n zvXAM8INx)>KcOgBsKbXda7>Kv->m5|4w8B__TeuixfHo>&6^7W_2tqZV^W;jq*6L8 zMNoRkFVRZa*GL)!#K#eJUw6USx^X$r4iW~tf?qG3UKd)3^C!N4h*V4A7mz4MG)7$Q zYP3=agJV*-lvRk7}Invt1L@8z_l{|7^}ia$HW$Zy}>LyV*{RtI5{o zclx6M`ZjF`zcA-DOmxWir0W9m!r{(fTAz>f_NyAFe-L21^}S0)e(FDkq_mwt`USo{ zvol!LVqjxi{=vbSxm>C3Ig~kov(R9&F^aKL1qlbmsR^%>U0l{ME5Nd=V~U0JN)y;Z zCI2n(>mb_)pVQ|4h*Q581hN@tnny-W+p2SYSt%NIb_6}utjbF!Th3KxZ>87m2Jq#> z+slbi4$-)HhyX7O&7&b6pr5FlL7q;k$%BV*rBTAO6u>;!+4FxLHzqVw+VO03vksw^ z0{yEys{Gyt9X2pA6Qqy%n^D$In&~hhZvRghWYC1*LO)obyC(s=p3{Ad->nAPx#6b3Su*7M9N}rD zlIaUNo;ZHM0PmA`A#C#={JhCY`~q?lE+)Z;yMK(e&~e+vGJwR)r~~G;%OLe7 zVdhg#IK>$5BjbS4ka82pqwx3T2Db%raSc?Y5QB1-^*%7hOHhx=)*lS*Y17ko52JCd zkjDJ@{>t~OLomzw)+d&}Q;CQSJZFB;UKzW>sv<>^QYU~Y-$g-XAZ{pdZpG97_wD{w zH>XyiB&&~t;71dJKdMIKvlz2bQ@04ALnY?N3)xKBt=vw|Vc7E|3oBDIChWVq1I)I{ zHaYt+8H$Rgas?F7n&SjfOK$_LP<$~Rs(+RnyUX?#xsa)Is%oqkQLi0PIF@dmnyi2) z`*y(J+?JPBo*r+)dENiCtn`zTmlyus4;ihf&%lB>*m;~K0PlC!9s=s4d1b9{`-6#; zoXi~{%Rm=m$E_Xxs_h}%*c148+V8NXJPSqoxIMp41X7~E#CNf&fh)Q!$vOAsEg z|G0=cQM=w42W|Mp9Zu_ECo{m%So4Zb=ndMV&NZtG;Z#;bFpL;I4mh!Z2|H#0j{fUn@h9#CK0!Vi6?fNP~bhOwj z>LqTZYWP4mCYiXNQ7Hw-XtHn7%FLP%{bA$3?ugqmHcM4S4}DdpD?WzpTkOV3IXD-V}#gO z8whQiNe!fshBSETJ2v5=Puk{wJjUbOc7lf3B|6z#Eso9^=3j`+h6-`NlmMkSPw|D~ zuP4R5wuH?h=hQz;=$dUcUD)sEmZQL*&DhlZGo-}tAhP|dnf-GqnCk&YIU;ut&gfDzYnK5o!5U*@ zvZ`8(^oef2En{?GDt3LOt9hrPW9l$FzpeAHP?#}8n_Vq#`QD7BEswm^cOwvXg0$$& zBd^L~En!ZSb81tM=`=24A)H_K8}$C*vglGD*^|t5+l94aKShd8C6$cKmwo-@&*89> z(9=*v8=Oxt`52UX{d;g_R`88Y@ZcxTI^LbBA^^z>#}hGe(&b|5cboe-7G$naZn&Fn zp6;6SxA@<1g%7I{N}AdfAJ7k}XE)zt@&dMA&cL*xpZg(+g^}Uio(Gy2qIJyH_pPx> zaSXSVP+WLJc4idX&%T?_Ow1(SO$#bW8z|5+{lOb(m=XNy)b>7D@=**jXUbh%#4yUj z(uZg!i{UctoGY8Hp8*lB&=~LtF(R0tbyT>A)5=6c?>$r2QLlB5Ip;XoE&$A0E5p2h zpVHO@(2MZA?>1}LoJAiNGiTZK!Mo2U6-HzAh;GWphAiUF%+I_L{q>Yo5{`$`X)`Py zBruD@dE4XRYD|)s3zdXAE zcdycSIDm_(l!thRw%5%N%+O7WKesQ$w-dK8ds8A{g&-_F$aYybpKXFrBbg%!#AkNpgDLNa4KDwY51(c7(s zMX3xUm-^ZjGFq_eziZu(hp1?Up|QWw>a?ueU2#1%Qm;_5s|Y`=u6HKeli;q zmy&&_iFh%1?33NcoSuzp%14~QE;IYtps?VU@iHY(JT20&yE{g~N+?MW&GuZZTX+v) z>CylktIBQSFQty)0KRD4(*AAR%@RI2*YzfI7K46>@Db(_|0!-dc5}|Z14VxBLE0c6 z8zsNN_d$=D;GIn@dx=8!lomo1D00G4Zm~}Q^x|O=)`m-PY&bEQO<7U;nI*aJo!6$2 zEQdleMctQlkW3=YE-<50nSwL9N~u*0!Xv*kJsi}Ms%1)Znx~IV-L@6X;B6PDV+9%D zfu#)HjVIFKHnNo2SVVOAc0;`Wf&%J7fSz5hb)K1}m9)9m=#?UNb=52jj~(|c5YNh+ z#aSWap&2WDUA&Cvu%=0!`0CVNe`Ft|Z+$4zH=aSK zeY>b2(ixs+3dU}d?JtHA!x5#dc2>vXbT)l$_amOrCz%sMaWu|CE%zHbjUN`Y%eDO_%$yliyc5|B($r;H240f5qBHsY5ko z3}mzK2?fPXe(jIj+IIg!YcDG7F%pjHMY#H0(8Wmi1qFcx}{?uk$r8o$D9ih2|sNkOc_-h&pt6M5b=95;KyCN5vKNe={4}pI99j9zRcSR3)i}+#lIXbsvxAxh8crssX0rsow71G13q& z=9M)AR0+D%#Mu1f%>Fj0{8S?|uM=#5Zt17fYtSx;`vP2b(sTZGN*pvbE)>eI&d?Ku z-B^5nk!yYc&Iuvlj$WDKd=}U1=PVjEasPm{UoM}<%$MS6x)IsoqvxNx8c0lY^J-c$ z+wCljbY*hy?fr!7WV!}?xl7cv9maYfaNO-nfb)dZQZc5wZuzL?3bQqFdJ5UWSn-R- zp=|f#;@<=uJ=fDnaSk*G8}U*Q?(jpC2RlC;MAS*^p=k{bT{b<9EI zbjrAZ>%MBHiyuZJ`}iJE^ur4W&8265t{^Wp2TTlD)<54#-iyMgO6Rn#;k!F;beNZ- zlu|Y+i-t#(w67$n^DFipJn1JT{e z+d~%t_}w7T+L6f=GL{`>m6i^6)gjl)1X_&7u+JGB9)Dy3ow_j{@2()a^ZH(`Nbs^` zOejHf!4xbzhKZ$k{4e{0^?AY3sX8vqk(6{bF9*|AW9g{fUObd$K=;bfPAAMx(c3$g zHBzo#pMw$Y)nNn>T&j+hOR!F}OkTKTr>3);i2}X-uz?ZiVcZFu4xe7uW=?_EXEIiC z(wyj-n;9Mka#*5{i_BG5El>6rMR>w#*5G+J%U-j3;4`xez_g zb416D4y95qce~Q_smjpyjJ71H0gQ{}d$|AkAJAqu!7*wrQ4Y+Ig78DOm0@c_)%AYC zIC--2zZY;pxvMD zxJ=5lZP#dbK0A$iz&VK*M@6Vn$hc1cHk-UKUol1c0Q5d^i<3Idw5i+V3FE#1ND%;q z(>}OAgiUt)%V8$>gx$K?;L=16nZ(%sm)tM?17K24GHvZ2<%@e9(MAfqp5Yv>8}gSU zbOm!wzK4_IRK0`=#tv4ofU+$tVF`s}48HE$-4_{;AI$<$#7P`K>q*WMd^(L+VGljh62 zWOMy0sn#O({quzsE-gQMfd=Ka17hVN*g*vea~bY?8$Mz@n@3u)DVJZosq}NjA4H!H zJ5n1aW2fV%`rh~=5(SkV_|7WkWXA*7ECw_ce|z?u#+5a%BXolgsr_H!&bMWI5W3=< z9-ur?jLI)X)HA=NL^IeE44LOLPk@Wz$bu;q z88if#nx&_EjGfm38!tz^#<|{)N?J^3%eAQzMD9O6!xK-XBc#boJ!6=c;T40N{smjy z&JG|O;Lm(3qPg{r{m7l{9m970Iv|2nN=Qi9znc+ErLAoj`Bke~`1p&;sgcNR*5&!y z;5U}fLxazQ2d?kFoBL;v#-Rv7u{b;qLj$1Fz#efeBD1#mbdXs3uhm!XdBRJ!ZdQ>dbBA z2H?JoAH~RwQwSWLsu_{GQz=xj813C6)Ey8%&J6?Ab3!o(O)R@m-UW}X=aB48jbo9W zRl5Ii0714SSGuadO>%qTfVkPQ>E22N?c#)#CX9YMUEvV*d30)%4_;AtP(#Q1)J~TW z@rPgf8xqrzaY_pUT;-Wegh|dtwhn7kJH3fI}h72Ggjb zwvA{D6UEFSARyxS-0!CHOf68<6{=;aeO^R>_f7FU!v|Odzj{u)sGiHyBZT6>IW&3N zqhfhu+`~56d6x+>xlJ>kG+p)8pG3}dD+V_GQm452l_*phntU9kWq$*8zx_I(Vawt+ zL1+#$Eoma3N!@5V$+6Dul0Lu}h#xy&H`Dz#8ow8-ysqbSV4Qy6Pd@AagE{85?8N8e z?SzAHywRuyzS&ak?`{3trRS(%&U2`?%ZahFt`@@* z)A8$++SX{_7C}`8J)Qb@Kgs}P=cSSlO)Y|bduQurFlHbppF!Zh7~kM?;ZbZlzE8YA zD~c4aZbiK7H9%Gc%b?^)wJrgIptr9m(2dV}NW%SbzmVsCmtnZ|(&p}P&OBn_*6}^k zcwb7u1EVDqAm624n<&pG1MyPl+NvV zSYb_Yc=t+cJ9<{zMVhXWuH1>6OIYWkig&}nPOnPj{;X6=&-`;w$R}>W;-|u-?MBiW zoc_8DiH+hQmcWM3*#%riUAG)-0fo6AjHiN6{dCQcONGj+OZ{|{)^t~yOq?ORv=twI z%|Hz~p;O$KmBK$#v)N1(dBZoCdUv_Lav)+(U0k}m{CPOQ?UM-lTM9T&Yf}ObZ;EU# zGdMBkX3Cb1oxhC&;*wkaj*~!CuMZLPwv5d4jC~W-$n2rX;8-kmvKvh*4uNUtRBsy7 zAMg}M+)sn;C*h@5A}u7YdP>m`Hk$VG0+H3o-f?qSg-#}*k3tH#e~gGxj?;q>|Ao-e zGZ%UKkW-!H@x2oH&&Xg0d#mguFuS&gb(!y_u8d1=bE>Gy`g~v&7~iPFReq_AHqHo8 z0u_XVD@*Xd1;OX2-05! z3y>QfiE|p)4Ik*BYfJWgjRn&&#~J=mqhebX-MGU>AWGRRD=)a`1gQYiYX`4nOuuZ0 zgb1|X$olOvw&?v9H6rFn=N@@xGkP>qE;x>BnnU{2H?2Ylc~2QU{Qf3q8=Bqtam&^h zM6gLBpqa!6D6Y63K%ST{mN7m}B33e`=%nX9y9&DrodCLoGQ3@O)yiXa|M8QOwnjyLbfbXEn~mC!l4$LzKRdNu`Xf zm&D|UcB-!T@_vM}a{!ucl*qc|-&*fAL89B*CKkLdt+7R3;M?6S5|Q;uW2JLhO=C8o z{Ko#%cUWZ-LZOf5yxFa zEaBph5j@xwZu>u)vvPPO_K;nOCK#ZvVz+GV;$YP)yawQh`*{D_MvYNX3{x)TXN)uM zH^t11k!HcmB4otD?zSUhkKEmH)eSiGE=8}(iF8C*{xwtIha=uj566UT-nT#~B49Fl zK0tqJy22O|jyKP=sh+!qyUn*j+H3cI+vPeD_Nx5kSv}2u8u^C@u-Z2)1Yfuj1+IsI zkN>4u#&|u*HNwCO{_EH-^ULH{qH4c0OtVzWGBaTSgo5B*Qw{^fnA(V%_9EmmuYcA# zjx>rP_Ug>;YDRhvwQc83$hrrq8#7#syje%c4RshRx24`0fmJdt%kQvmvgf4W#3MFh zhyw9%E4mwogJhydli9QjKJTy+0n@2K)!9y}(fFg!MxWO#hMXVkp4Bs2*_G{o0Y_Ltf7AOziUx#~s59v(kGrMNuT zCq#NQdQ|tz6(@^rAK%#DB-*jDv9wgE3DVhN=KF-mhgAnwZJ#t2F`qOZ)mYseZle+~ zd$x*>yXlVY-!TKmlm5_B^71hu$cqteWQ1QHXL_G4^>?$`(I#-ZZ;)`I;tP8p-T6|@ z=eX~eNW2zZ9uHu|Wg3AFT+TI}PQBO^;wX_1awDCA3ew89AEFUW{7%<#zq$J!JDg5f z2u`}BqS>LT?k@lJy{o^tdYOZI)>utXux1(!Jn81@V~3DQd0?z*gF&-Vp}SFJcW|O3 zw{Fq}-;86ow97OrmT$Saw+U&36OFA@sx*0XIJ`p^h4OeEc43wMQ3Pb0E`O+Y8fkyV zaZ$QESLttF2mgoYm}P+144E&0{l}@Bp%P=W#IL= zW~zGN4e=CPXltX?VuQwWpJN0o!K6E7*eu%k!~&4#?&UD{rTZoreYd9D#T}(>E@KM8 z!vHpBJm9HAvmNUz*T>7}2$(C9O5B(8!@)Smcdnu+tvGKf6d&NY>5qymDkl>6i|y4T zkoNOBsa@yxDjehqCn-yTR{M!%D0Ucjbjja7!OS1a+~1rM-Oten(?maOA%vKS)T(#m zrf?{{?SFu@A2^uZVDCBZ-@~nlwE6|&wnN z#sE#|6-U5j5>@l$W)UCoDBlfrZ)lf-wyZCaXLjugY&(WX-%getT5$3Xy z_;xJR{>L~s#OhwGbxws+ zfTlh!y_wO1!QJi~I%Q7TfdK(L1vMd0INs+x)%Vd>t6R$Y@0_li?xnf5vWj;i9H*nu zq1aN?QWke%z(@*9z-%hSk)rFV<*CS#2SbB34@I-fi;PD)r}_4;hR%0BjV_bs_nQn$ z$>Q5`9hOJ5e-2-HI$ZiAK+g?@0TNyXc4=9dEVvcaJ!vg$y4~JXJ4mP46(~P{8q%dG z6dXf>(4SqU%Q6TQmDF24l>dyF&=vZNhQhITPXF*zSM8mn@ll%j08*T=TYl%1j`%+w z_AZ;9D-8tSc)#Deqe;?x$U@{rKud>4Do@6_19Yj#c4X-0e#OPNCHP6IJLLP zKNe}I8E8^gbOUt~=$%}~4CQHAj1c@0Tgki;2?9z2y2H6Iwfpqmv+;HKQ}0sDS`#JC(v)(P~^5S^y0 z?C30tm+vFoG-@a}6|pN?={EJ^(fy+{;Si}ng<7;QaYdZ~SFz*D(dDXIgWvr$h@v}gg zkR*)SAu^kq5fDQ304IpYRq}`$`&r@=Qw^@V2mPInSTN-AFr-+AnRCBHi`bV7n0WY z(iehgEon)^;7|%HMWD}2$|e`QijVRBx0Fa$3KQ&s;Fp)%{zyjYD_wrjP6V+Et*oCi)VKZa z1gjz3br(;_)BQYjKTe$x+UZE_H|$4toeCUS>4N9aD&W?UaXDSISuY|ao_~#YTy-tT z{wdK7MVnJu4Y{JKy6w?fRL%^5nm9R6t6B}9t?(|I>5jqvUbpSFmHD z8IPh%MXr4MEjUHYrNcMBRA~b1GI(h6$ssgK1lC|j5RSyM#309djI@#ME3rP(F3Nuj z00iiav)rj0sTETdUwf=!0+pA_KCn|_Akypodaq;@%|Y0Bjd)qg;g#nbWdSVIKe zVvlrv7nmKt`e^-z{G<`!^A6b}ta!}yEwgQ4BFy2PW|~0;A5B}4on}-5W=UJYu2M=v zqs=C`^JW3kQ&J@zDunjCabQz}d z;z!Ft=0P~C+_8}x!+M;5CB0hS7Q|-oCT?C}w@I|wUW50#=aQAfcqK_#q9heR1(NbN zrsACe*6^JSM-E$0cDA&8O|+i2`wP!P)WK?t;m~)}oH?%J0HZki37}YxE|ky(=g{2f zdUY{T4$@e>Qz8H1Ve{>3V%^!&xVIOy9my1V+o_gN0HssziVD6;vl;I(_;B=LMaPyc zDa&UpN*39Afeye|ujtd_4y79aY%a3ED>TA7JQ-HiH>m{q|5M83jt zu7{?6JYgJkyN1?3Ww|{YheK_cHJt~+Ql@qFE1E|i4EFiej3GueeHvHE1+GB7uwC1Q zK3@JinoI<%>oEZbIB%u2xH$;7iwg=e3RZ*!NcfC=Z6d9R1%kHlffXw(Kr=@K=vn)w zyFV0MzQb5{^#vgp`aAuvWX_{0(hp!<)lZo9)%rbB7{U!CF6$2O{HTh9M+SsY^Pnl`poQELgN_j;CG4FY^ z30A;rj8@Uhz^6G&YQTI z+YWYATS4-&j2rvq;Y0z`kzC|C$B)zm3+>jh(O^8W4{Vqc_hNYEfvC)fkcDTIx3!29 zEn2wbbus3BWqxD1;Jcr9h54)-w^GIR5Z`oj?PAaU{y0zr8j3g5r&oT#&C8b4^)$TK z+cE@dYS66ugV%eZL^v8dCx_W>o8&8%qh|TtdjaYmxcObokhl_)z?c@J>erdG9-eFH zc}xMUt6<0nvji`vXSMslhh^e{%&#)*@Ggnilz$`}y9Svi_^?5j&&!H6sfkudrWQf1 z+XfpH!%{?ebWHO2sk9#(it2?ADn6*HSRI)I2(nC?D2SL{Ay zD-ffd47YFjW}j4Vy@V3$D0YPv5Bj`+_PJbb1tyOA$ z=h!AOsOtx?WuztO{c}Sm--3^9bXiVKEzSNm^vLpPlei5}>$iM>j<##Oe7?pNP*?hI zWU(xZieL;n(1hVPKydlZ_Bx{xGRu+qY1*?jxBL3@pGE*B2mF{05lV!isin7G;pp&i z99=;Gmdv}$c(&I+BZB|3QCL7LE);tRnElcR>LLpxu#T;Rt`~&)d1_gcvP7#kF{(s% z5EqCX&)D-G;Ta^%`+0|#U(?>Md9p{%D1*h=CndP&OrYXF7SH)nBswi4Cl?PCY7qCA z@nb?i1h(5FC(q3;R@Vr%e}(}ojepFMAAk9H)+R>U#0Lllo*6Cwc-JCd5gZfLwKa_-cXBsZl;V{;6G!N}1Xq|Jw_peUdwb`#&|4U3XPT zbU6e8rS;0!<2o}&lPv>){8?m4hry}j^SN+vWN0D?7F}g1l@(Og`)^E$OLZgWk?#Gf zs}K5<5AxOUzYE-idmD@=6A|#k4=u~$o&d|K>4wGd*$y4eyA=t5=7r)=MITI&aIFh0 zc{^PCF|K0u&dPyJg^!7J_}YoN4-s*+)Ngl$u99%wX5T4p_X9N3d2MrilvXZy<2I{8 zWP{?)DFcOIgBLunI@D~sUViNn%jX2@aUBVLSXfh zR#{CX>a)!q_U)=~PiVw$8;C8yo_cN={w91&Y!>T}{dP0U=X*1RP0r6R8fdoV$hJ(U zU+4`hg%Y)v(Hu~05ZcDka2Q^8YrFG|r(}E`cA|n5q&1)w@JIZ*FG!MIHr(n&+hzf- ztKsAo=n~aQxU#tM^>y9_?Pn~O{N>9NdXh8A#ELM90?iy3m_Qy@0C8sv>4f;E25PT{ zv0#X$^mJ5HDQN8KtfPYoP7@%B(s)Tj0!Bg3iU5wlDto!X|MBGmB-gLHS86@mJ$dBS zraHTyesMXUYV9m@KdGojvHn^yFZX&msP!?At@0SG)~)HhJ;TKU8AbXSa2;BaPHRFG zG>%9|k%aN{Q}`ne3;BVJ{t0NRxdZA5T9cs(^u4=SrfRF_D6-mrG2w11{jz5=)erXR zj=iF~_z{8~`W8OPe$ZS=it?tH#&-RfSwY{8@xnD$zCEAh6-`Jw+~!Q3}W%f~a)`Sj1pJ{nkJuuv>rT@3%FI{k^QQ!&*Aju`v}CL}Ntzo}PpqaMf(xYAdKUAuw3s(MQ}JkJiMn zb@uy7A}Dr8i1+jq@tBX!S|hi7>i>QmbwrZS>xk$Vpw7yiBG&o_{?jzu9PC=FS|=8N z+J_fv`IKK3FbxGios$?LDH$qudQ{lIisnkwW)8z{{NNyEvWL00H&km$$xIo?p3-d^ zyELTMJ8kgKh3ZSS@hNl^2*=ZzPXfAWIHIn?B0~2=>v`d?D$2mLvp^Yycw&!7=!F0i ziP9@ywUf{l_g6m~D|j&| z+1H4$U<#-bD(aRC^$m&P{jIfHR|#D=#{w0|xdjqmBH?T5J2l?r@HyokzEOUffPneV zl47Z+jO3GbmM%)}C46f(-drMJpZc`QddAG|8F=W;kv!jnKs{VW-zoUEaEInA1p?hr# z?owr9JKEx#OMNQV$8b()cz&2uF=|PVLJDJ8GEJ>dv)kEe;;<}81ZapD04)XTs+){q|~=}Am!Bz;y@a3XrqU=$zfqN%j{7P2zmgMBw} zvWO%)pSr$29z%J*wq-cBK?T1uuph>Oo(|ILgyZS|ZE`=n%TmFQ+Rl{OOb9g}g*aVP zxv43i)42_I$0e7>V;@B8tO3Rf)?hhtb1%kty=TWm1HQH(ArXC^Z@ry@8r`zZ84GE6 z!x};Cm%C*XPLc|punr6v5?Gxya?j)Q7`r8z|30h;0d-$3Y=neRW@xvBeAoZ%Oh=>n zUO~>w>m9K-*t z?DFhv-Xrly?F|*?o-{|l*?Z*E(3#tQx)}uoa9;(3>_K`8im&N+-lhX*l^1WncjC|> zR>_*XsB7Fz#ff_J>A|;I&0C9*kj9DnohYXr1v8G@-ySL*mqH-zO7EhL@cOzWKHPxX zL*=uRZZc%C12)9o03!l7+@2j2?a|J_S^CDc)5^CEGzvH)UAXi=c}@298pdZc6v<*T zX?^w7^a|>SDyOMA6qul?=>1xyiwR?%LF$pt6RZ)bWnN^_LGKkj%~9fw>uW8i^@xv4 zwq7&SZewRv3f4s>?aS@$p4upI(y}wMC%dYxiMvU@IgmX^fl}o?#6Nkd-;L7`-K%<` z@%`__E`e^S%@7Sw(JS!mZ;N&^ zp4-_FB}CoYp8wGv=!&H;EJ{iE4qRne{~Hl~4;no-C1EmRC((fgJrN!p}Ux&A~6^^{F0Cv0aIqvscfVpMsxys zb4LGAMZ9$E<6%7EVPPpz{r4di`uy^ZxN$QBi}&MvMbozDQRrvZ<)uyKQVx91G~lO_ zz_-xwQ|y7Uk<6x7sP8VnoYCoJt}`6u-<_Xewyf?pFyCtGT)Xacd|qYhI;QMpcQA6> zh0F+1aw5lx)c#QGJQDPrxUBrh(~J-kOqP*3u9L1ljK+8?jgU<=G2Z|(=a#@|dT|ip zt30Nsk~U8T7d*lD9cLGy3$G@ig@f49OKLOks!|7iMy2B4A^#2YLqosgx7Q)cK)OT} zU1&L~rzVYF#O(L{f-tdm85mgw2!JM$i=hUQ-NKI)Z$QLlC6;*l$ zC-bw^%E#oSB$mk+AL{aodKH`)l*(yK`k3Dv0Rq7f*0rfm1X^7l**e}As*l%X_~(Cq z&eVM%bL)C!KxuRykD7cKQ97s9 zZQAyH6sZ(XmkC-hYqxF3vdBWhFP$+%edc$EHB`#8L8oxN$cjPWh^}@}S*2yBe~diR zg(xA-mtfQVGh?jwKohA^h3 zwY9g)6F)M5HP<;R+j*L{I=@X-1nG+Z(Kt>Ov-Co7;Z_uN7C;*;KWf4P$J4;~eGoGV z>!rWOj^&*^7emM)>JlI>s+)v-bh@ywr&|h z6E@9nKF7GO*>un^5;5u6+%%k&Q(nYueJtCw-$J(-TokrgjiCWtb1_>S4jcg9t$*v{ z>q=fdDzU4@1<4_A5c?EypASQ1U5vjEbTQhF_21^T$%hCg7das#)Wd_>7rVI!6#kJA z!OUeSzhrXfm>{Ohx#N~53YEmF#=Bj^Di~XV4TS{E{% zaXDMgAYRgD!ed)jDRzw|s7_Ow{w0DSeDuCXF>PwG0{a{mI53`OM}QVc!l1!S3hm1IOnzRsw&D8 zf@LFr=K0PP#nN%F{#Ub@hET3aX$5WMn0yyhUX&xy(3^pe5?KGBUKKGvDM}G?Jq*vc z_KVb>&$i}i%lab%$EIwz(|Wjt!KRXR4(C=&LD-8Y()=cqM?T)u{e_FPpL%8=4Tffb zmZcV}&ENPfHua%b05gD7kNIB@K&N?^vooJbB-Z4zt^Y;+n;}4_Cs=uZ(mXvFqcU44 zjl>b7rrXS6=?7P1;;eT|mK5u;nYe%*V?u+*Gm`)Grp16y8}+VTgU~f$#G#@AxZAXG z8Y}{{`=8zPNKp@86`cwJja~G8J9)_RIqF}^KB_BY(#7i@#X|aDL6iU{u{seJDp*)MXue^ z1p(wL)9vTRk$|ZJfAgkClLq~GMaP#zD`TnWS1~cl-8!w9^bq9TAwbOcYZ;btL?5?6 zQus!M>^*hnJ<@ukj+t+_aF}E@@GjHmZIDtR-v?l`^*o%+T(pa6nJ(~f14IHbAXIMQ zUXm&7&9Xz4zwVPzRy81OI&Z`+t*jQILFm;MLHY&Pot@|!_nwahzYb<<+}zK!wk#Zv zCLcbDUVWmZIALwmO!@E9KR3z#dsUK7aXHjEW@ z{@WUJB)vF##f%%i?+e5ibpuGv1F?MC0X%1?&Ma~+B&hhd4cfr|whL&f;2*oY%Es0_ z-K>#u{B3v13~W+O@|^m4M!XA1>HMKHUtSfhR9maPdMV^@dxSkZJE?1{Zmd?7AwHZo zZ|1IUwCdYfTi)2PYCKpW0ag25Rnt9u+Ei7Z-#w1^Al4(UM_1XTh9G$CDWH*G|Hojm z`o3P^M|h$$99`}+4%oz}iT@2GPOWjhG|?Wpz%Wotsfp$u2mR~-$RE|MQ}4rWR_TEV ztxD>SD$?qgRIttU=~^yERC{}Ea*|D24@`O`)wn}T9|CnM2YIiKo=0;&epqy4#o(EmS zH(T!MTLk{|x%21S`^}DEi{ATt>SM0nyRtyheYaxm;WAsX{|WWKrcnFkf%XHUwu&q? z>Tb`~saFLSsCBH0`(g5XB-NYC}ahwn@F;w1-zVo`QQ!xiGx+)Gi**Y-Y6(YnL?) zJzF(@00%w!U4L6@09INpRt;15xoCQTs%q;H%qTYg`U8wWePR-=y>eYI^_>#_#@@~q;Ko?;A=H*-B@gPbHIlO9t>gQP^#s|}>7e$~3|xt%q?@h!QM-w-F-1v{sP7fvzF|01 z*-Z03Ej@Ks##(Co9F_%gO1f6LYGBYOY}LKH_pP~XAc z0TAT41CUXD%5O<@jFQU(f zst-FS*nQQd`Pr_OBBueQQhGAllg99#93oP0nO9(`HiI5#ZXhV<P#_juO2?;LXi83&iA)N;^G;f#OL_iVI{Bm`FqI7FFXzh&I>MVq+WV!wB%kKEve8MT&JW${CDZ@!1u(Td z2rUgA*)uNs5=Y5*Gue$_LnREme?OIsdZFBJwbq~k7s+up>SLWv__+VdL2X{t8K*y1 zs{j{tS%XB))viBdZg7I$W0FUgCp-H{yfNrDp6K?OJOIBPmGWud(pb)Ce~$pAzdRy9 z`l>|nX`N~~egz8+f2%~q&iYPtZ6Iu6hJpz4>UnMJ~U z`A|lN3AchekrMXhHh3t7toyEFnO~chJr%?Gjs!;CLAu`e(uyhV9Vq1-xNcf*#@2N( zN`o=UF`t`>+>T@f0=q2k9jZ<+*JLVLP{Esv(Lu{$G@traVN`U0wQ-J&T`?l}XEC)X zqKdqr<`&=7yeE}16!lWkz1R88h!dgai!#|nI+6zH_f(WLSqL1mmFg9iU(H&>X6#19 zGUM)sFQ{-M-(^xfj?OC$d+M*{X~JpIq?ptB zk&<)+u^oR|{Hd^Z^tp9(0a==LX@65nc(Z-*6St&PGzmZF&VlDrBRv^GFphSfY7;S( z#+kEhB4a&oM+4;I5S;kk@9)c7M?(tJj|f?r<=LWzngn1!h6bkI?X@G=y*&Zab%!vA zm;o#+sol@t_ax1S3uccTqD*ySbXR90hu(fO@csP7jq8u!I#% zS+@??A~NMr`!RmvJhkT~S?7j0w{@t4vEg7M6#)81s87Fcm0V0N`x|w0)iT&~z71^{ zlW{UkXDZ(^ZKnWS-{EY9K)j4Ezz7xv4s`#PBu;cCm$h$(>^XOPZi0whl!s!&9}8L+ z;J0-;{fjrReF_&1=`W6mI#R^n9`^eEUlYL4o1l`u?HSl%*Wu;-D(%aNv&jmDS`%ZAF{W^Od6RGd zPqtN%OPs-G*YO&wmS;pOfW-S!Sg3@0qTTM!6cp- z>yNpTSw9;gVsQAjPA^Qd2&qIPQ<} ze_kXx@+GJxzoewb{UZ2zkhoQWJy4#I-%rEL*zHDvauq;#MYgI!e@yWJ_{Ij3EnDDGp|@4Omo5dU_@QEgucD@tpj7(o_^Yo4Ir z&vrn<(^6Ju?z*e9n|z#VELY%yvH2Ho7KQ}S^CFUxm72=Sa6BF=yF9nHJM8Ye<&6hL zHIt;)#ka3ikY+&lS4oazAy|Pgg$<9Oy?BGOA?aew)~i#{>{=2^`Ult)@BT`Y-L@be zn|$5nWG@)gy+BOuutS2(E@Cm5eKP?2e;#&P) z6~vGQUf`{Ke79gXxMxBy@jW3pFykIFs*j0bZ&p>W`Dd1G(+Cxz3G@T>)&08!Gz&XsSpU2N$#W>c^^7U z|6*`y!r-@AdWuk4{(YCh>a=19CbV~biy*KNeZ5=O6FgqcS*MX^(~{2f!t-N>Eps1A z_ZaXbAENwq%1V6V``E2Fx8d7?qrM29*f^X*&~K+dGwP@%;vD}b?7-#kf7k9zP&L)k zTX8Uw%gGOAI-a+>?5CSPdm}Ke^2)uI)`1qeY;-vTn=3jC{#EK8q5&5T1{;oOLAO&J zY8)Fp;o5kA-kH+;^XMVqz!rsKjBGZ$02(sWMw3$4rA>z?Pbo>@5-H-30!`kvY@d%hPABi_{BZ}Ln2sRL0V$_ z7dfUvxAPgH{h{mSv=v7|J(hUI-|t!DfCRdki@@yVo69bez5a(+Hv3$ z;He^r+VLImHGz#0;7%IdHDcr8WSM4U*uDY7+fqBMIFSElW(>5NtrxBuVwY1YGk{C` z!$PhWPhbJ>+SWlWX4^KPMX6@B6Ipyqm>fVcPq~&pkre{*1Z!h80l!#XpJ=#bCAeYN zsF5FJ<$T#+#_P@X& z3C?V=@Ig-iEN3(N8zf+nXb3HYdg!A7Pv)h1L;YdE5ttvM6y6uop%UyF)8d^8q>AOS zkgL+M?C#!uX!7}&xJWzrLki#Ih}}5oW2OE;-O(|TM!hB@rGDH&L@o2FMBr?|6Rg$4KeRQUmvh@Go-1MvrU_SS&rvCuPNQ`S7x;iZqAC%C8S~ZbUowq2ioZ7&pTTm;&ZE7McE|UKmMVY*Xs2I+3kXb?zA` zjr?{k`NXajLqQr+q~w#gq_A6C<*J!H@WR;;-4ua0R1mmx^amcD3Vc~F$`#sE+?rh)3!dCp&SvZ?lQAmzbm$U1z&|i^;!y)@DRaloQJP1v3Db; z&{#;L8;M}Ax4koR+JAARL^B|PKH!iasRQUyfZ%E~aQOVVhFisLnjXXbw7nEqefL4w z0PGmF?Z>XQ8NuT-h^3_Q){U|!$I%{tBOq^mq{MY^P5Fq4=&J1X?FLJ_U2u}7uEnXfV!)HrW93%QLjI#^A5NW!If7t`_5}c9i9)eMkE6Fv zrCXA7#5qFM6HA#i4GoP=V}S9PAVpaU^-4cPlBS!Q$C&JbJlWPJl@&SJonHbqH4iil zaxIUS*h}2E>anA=v>m>Ma-wr zJB*GHwcL9opGr1MVp|dz(CGfJE=>>PSL_-e(3CU&uPlWEgPQ7Xl~2t$ZR@F&uxqdo z;PBV;B2`i5-xBix_tBP@ynXEClcLxz-aPz@Ne->8Kwl0&Xx=IxrXQA699We%)v2Yu zRcx&2Rd33Oqmz-uK+Yf!lHxNGle2RcwwSvX3RNTHUe3*I7-+HDx(FaaK=UC}bV5}= zGi;M}cl2P~kcX9xExKRPp(?JIjI&TO0rzo#$UicRyZ71p^n0nr%$IjydqyM_OQi4y z<=F|;3QEA_(>|T_K21mW_b}PNVpz4nu_Nxl5K!cml}nUAD}%FEo+9nP=3Zpfe!nRa zv}vQT--~C+$5I>(j^e%u(1XIbWXs7mVy~grN9!;d-<%F{9K0JF=YeyP;t_iV?D&!0 z{XFYbtElOG*jQV`wN7uNJWY)++!ancsczrsdc@QF;hdpjS(_~+j9k?~w`vS}C!>N} zSb8s_=q7bn1%!AF2El9-7IleooTJGsz^io!MML|XXP7b5Hlzr9JZ=C{@xlLMV?R8o zXu?q4E^YbDKSqRpK$v_S$yEA&8WhAprw}~B?Dv-3P{ps^9Xo^ld4zeW61#C+# z2JKTG{f;DtVe7X=pif>0!>^^DZ(c!GLf)6yJ~0I9iks(5Sen zEFR%muj!mJB@>c$|L<@D|2rJzmC};W!`UN*FUm_MD|erS-+NlXXE*6CaogsAdL|H& zM+FB8E5q!WN5A8eNVP`7#5#P;@QDc|S*A;C;%jC$L*}uOGl2OLSh)o_6QhHi<^u$h zZM~x8$LkQy{5|CpdXOTr$?ZZrBJaQ^jx9hO3)!fH6j5vmVzk);DWYG%AH@pp`uAje ze|R2C74xLGQ=TV=W3C*+slesL=pryAd0E#XHHmW7t*pMGkCe@=30A;eM0#9$sI8_U zy{ns7YSa(WWix0BRJ$ndQfHUxqPzB$O^f=Xq6{{ELlxhls(<`<8j$MYKx zd|Q5kcC`y=9WZRCIJ<^kr|7|PPX9NtAq8tsxi$`ym>aLyP(J<)&t>?2^i{#+zr_GZ z4V#fvk`A+~4P?---wk36Lr4}{Yfvw|%>!RrWrAU?pd4wZjqQhv#uc9WD%@X9CPB}T zu9;|1R+j^5E~@IUNI6ERn)BCAf98T&;vA_~y*(anq45&G(cO?5+*io`#A3V)9pS0J zw*k4EdJX)9+DRbitHDU~;%5}>vPR}eQ9wdg3tkWvw)bXEt-0+MQ&AOvOjFpMGJEpa zgF$>9$s(P5!b2$EwLv?Ot@*KbPUt4DZJxB4I$esEfZiO+Y~*9Sn6JR4;$DkNgy93m zS{I<<+~MIilUC7kd{%bhr3GNm048Jky*6z=Tpuqa5V0NsW%_*R2JP)4G*Q;I%-SYG`BRO z-*L{|D*zp(he2tYKFk1V4jkdSq$#3X6v%%6l@KdaQ#JyDRVHcGav3xlmH9CtC4bH} zHs4+6^L0I&&kirRAQHDn%`7a z!FamVq^ffgM8Y3u)F#f`c4@nP*SI$Ji?s)3@p7T>kdm#e#$K~7e1><6`9P6{e=GW+ zd**>&Fde-DObBb@Cg6k4=L+_2iErhhs%w>v1TG~l@88^z23VwGvf-kf$GL=oM8)k8 ziR2&d8K<28wYTt}HCZ4f7YfT~d#eo&AGeF_?7)E`d;X1VqhBI16`iyj!jM-(_YSH_p?#{ps| zj0FzAy7!@M80rQH(|`UL3MgVAK)UoudGhl$D8h$<*^jSxdswc_#01Tu`&#Jgtmpix zn9#5K9>K4K7UkOcqSNHDV8M02Y9O;VR@jhhQw;=Fu+ysqS7L+A%i4fY0?Jv>4ft*Mwj*8w zdQI>So}r;kB1IJyL(?4RCLEVT1B-_`-Nf+J=8HPkr*gfIF`wtToTH$2nvmI3 zhrOZKCcWaZ-q>!_;Y<(0wEsSt3$FiPuNC(nNWuS$hScC=C=C6Q%!P!>Fwsg8jTqz7 zR%#=BNcfvk(6g!Ddj+h%y2v6>7GK1Tq{XWrQsR_qsm1`NN{&?s+kKYx!dRLR?~ zMbuPko!^nE)J>RoT03@8w=NVs`31HW9(xFU+W_;H024b82A#G~;EM^(U(zwPcPRHL zW_CXFpR}to(#QW^;g7Eqe2Jf>CqI9E#XlFVJs8v^4CxwqS^B3P++b-pDErV3HGK1( zsa+GN5b1+-m0Y^&JZ%GtkXv$;Cf9UuoJ4l1-!J$KQw4g8J0Nr9QLoFiG zHgUP!qa@&@h+kJJWTsOAr8-L^V-O@}ufe>U#;0;YB(PFI^^kcgsv5y=_=!Z?1DqLj>yZID>VN${o@Z0N()G9BM2s3gmM_-vybJ!gOIb?EmsnI2 z&BkT`+|b#c!|gs{0RyPVc6+qkvgqX+z{S`YDgygzYDML@L9i%_VNJ#fUeKDK6sNQBIo(dG)9yy zrN<{UVK*4bNn4DvummXrO-1os9x$y|Xe?5zM`Y!5T9CTp>w8yzKY%3n$cwHHX(yRL zJQH9ClfzOM_NlwBsP8o7UY2D>0uv&HUv0c6OV|On?8ej&6^S;=tx~4YF<&Yc4xO$z zo~j&ljJ4$3^o{{ie^T1;!3eIp&eIS5tu_2HDgh(?U&3tX1!af2lO=mfga11CPe7FP zi$9LI`nQ1i@$YV&Ikf%uXu)g`S*LuzLka`Ew$avV_E9Nxe~c*@rCf%DhwW-caj73Z)V}#<^Qpbf#AVK+eLBp6t<$(pGtHh??snbg9VRvgaNUoE0|{;1 z-FVNHyWy;bk_#E1Z72#wvKUXrqH32T?gD>bp4oP*V*wW}=X9)z&(7#J%b4y}NK=*t z0)5v!n@u~}leqN6r^GfIcH_BhpNQmA6hHGnv_C}@sK=4s9+Rl}A8qY3F*HC$iLPVF z=qkAFawf>I#P=o>L-}%;eCrHY%_DyU?nH%5Wyv5peHZxi>jC2eWBV|5Ds;jLHm1%* zkX)HD5M$5j> z_LN11gd7WjK(LWaWlEul_0)LMc9t(y*qf7{^;U67;^Gc@|Gp(qs;EXa!w-zCa!Y+0 z=?A31LM>pt4>|cn+wU|I+d;yijGW<%!2GYSQTogX>DMYr=>Pj{pRZs9!7iSSOgK8T z?^5wmbQW=6vJNx8rf}pLuY0IHm$#z=RxrOfD?i}eE>e)2UYLGgQ%lWK#j4w%QP5aO z+yx@Iuc&QJK}FaY7HB7+TdqZJ+6ew3$OUSVl~x8BLtJt+PQ?x2Q7{FzlT=XeI7MJ~ zy-d9VoRuF@Rx&6vQ8`v;bOihx3c@J3{{tqa89(ccQIxnkx&Lm~(KkJdZ*!RbI3|kIoUSwM7GwGSpKPNdmLEbF5}p7bw~erPv=uE0z`(IL`j8VxF9x|71q)>NIV4 zc5z9uAleE9(yHrh(W8%D8*Ro<{u(^M7!RY$e(`u2m&+M*Vpf38N0-g%bk{a0EMz$~ zot0nGS841M2N*HV5~>;F;NS>sCoVvuibhXF$3YvcTYzL?e)qm^)AsgDH#cfkaV>f?Jkt$g9L7;pR+y^BK*$Q9MC|uj;yFIj$aB zpO-c7bTq&a`%J`kdwV;8_ccBiq#@_A0o-ewSmNsJ-d$CHJg4{2O7abUJIKz)eR)0L zv-N%K#{&(*jdhELalOYuD;1$UKWk`@(ksKvauF;QNbb(F9p9RmCRwdEtJ0fS(BEw8 zK8>lh?F=js#EG?%icCJ)@@}&A>(m+5^4#|HH~r2*;q*T7_J;#rT-7t`zFz^(x;!uH zQC=$T28HoF8^V?nNbDnC5>h+Vb}POlA@t zQ>An9^=CoH72RW2Ud}A_^b?b%i7w;W_cXnR1_8t-mkdgHMjYZgV@$9E%pUk`W?qA)pejf zN&j@86H+xDmFj79+Co8g5%n%-niNt^YvohkjW{lk2KPg3T{HU=8K$162s@}l%qZXx z(Drm~mxODkIo_wQY*`l|e>4;-ZarJ9ov(%cL(v%(?d5Be>?a~Fy%1=cAVN{+4oY>+ zr{qJ|^Nd7VSl-@{gR7lWN8?iP%Cjp;S^YdWZOiQ3`+h@<-MIML2&k;sJZWxaQ$xk; z8{By)Lf7zp@Sl*e@OIp0YM7G8v2otq^0HCR^MP+uYWJzJ=DCU~$WbMB3C{QjUlJY9 z*S4)&|nrKoYLeS=%xN z6IO&mzUU`$>Sq~a2kwrq_spT)$5<-pqI>fGJ~^P=_&|7_>a0{Bn2MqWRq~ z%8an4oh4FiiAPKafPe->llsk_|B~_^-p8!ALwNf@VVlyzkA=B6A*9UPq7rkx^JvXC zNPbbu(;>$~+KE8lc+vcQ^_7p)hJ-F1{@@2E1?GT#1$AFNtC>|p<+3_4- z=2G#Z(G#>>;sHH zngJ+RkAdmlGnT)T!C$vKmOMbq<;zO>>KefE{`leoz8W@=gL$Q1RkwE!L zz|k$++Z(-91CjgL4Y@Arj;)wpFL8IXBCg$kYlBLmACnZR|Fx0w{JDSJZU4}rUW&7E z#ePA8`rx1#;7O4L@AWe$k463WDo@c$qBXk`vpJBZ!}Hx60+h zMq9k)JL=wvYbUW)C2bvhnMxuelQ8&Kkw=D|c=Lb#`xOEiSK$buVAwQ*e}kNg8~1gr zeuxZ+k&};t%<)$BBjEch(i8*f&lIf{VW0LS!fEG1$aw$soWr=(kAjW}xojv1g`?du zTD3B8-rS3UT%x6=YR0n6>3juYYD0H!M1ifdr;w$RJ$I*ua4G3fJOkaU1~|8 za#&E$08uW(7r<=aa*1nSW%I^wkj6c&n@GXwMu4!R99^7jhy(~2*vfSHd`e%|-k6$7WTM zLsjG$NT{x=)q}~rCDa{dBGXN+E(})~cO%A%v5{1%4UI4cPwkmmL~ZtnhJEXbkh-u* ztN2Gb>a#*21Bv{I`f&_*<$SJ@@P_5MuY{vL7559)fBEyOUKqLb%VsCh^@mW3{IAgE z3imT7R}uiH_J?P3I?v& z_M{`|+lzX{xDPn-YN%$DpFbCZBQj}FBIB$NnHJuKvl#yX1aK?I3o@^!ms@dr<9<6= zh}mQFj(YMmdb3}Z2UDcQD0)+jLY_5FbJKKNG}^3G!Ov&5L#!PDJUEXY8Y}EatZVuNX_`(tCJlTfE1vSV5<`~CBzSq4x9_~fL;mXD0 zMpAhh!4T9<+(=QY@o&=se@)UzulEH{Urv3i<9l+*d&S4`sj{LJo*&Tqup3+wix+#% zau|SuYX!5$jevH?e8l#WdT;9!VQv)#!Y)#YP+_dJgUck0q?*bLT7<&I^CGyWEdFAW zLijI!^&kuQ)yDGZed1!PhMa}xK}d3!3@P3NUKAxEi?5ID3Ra-yAK`+Wz)ui73d*1T z3iqgI1Wl~#jHjqT7HvG|B5}{^7gzqPcS4(^AY-Dpcf@#W2{hf^got=Ev_B&_ zJWN5PD$JvBp0&E7J~4f-+OzZNZgL&2O*dYIC`lXFHLT%ai_@z>{V(H*rTtt5DKHpg zI@Un?c;uIDqs-s8=xpaAg5m>dmV;L|@aSI9QPhuuHHvlEq>dmu?~<^dVnOXPPA>;; zeMwh5Eex_;C100&goqUygC{g?eHGsD zpNW&(j5-SN7&bdNLs%Y)=*jf)(uzaE#Iw%y;A!mmm&wi4tM+kt0HN;%%$gtmZ&Z8+ zm^2Ft#Y*;MEFTICverKbY8gv^d!r(MiBP|tD7^j-zZ$9)A zg7SToYM4@VIhmVCwg0P$8l@|x6bSj)f}-zYe0S)Zp=sW*0PP&8sh{MYODz=*TlR=I z_=j>l;|Nv_YH$7Ziz`0Q=}N%Q9hJK?`*|`NlcPVjx3Gt^VLk=+PzVrQw zU2GkJ&|Nho!$gc(>9H#KOXAN>cIA5LAf8dnJbxh86f(7gE>u>)sSKx-^p9O#O0Uuz zBv0if1&uF0kF5*f)_xXKLEiU52EOsD9@blB_O(luWYpyB$PD^;qR4-shsD~b>FAPh zGaHa_3<=%c92MEG#NqIKs()&)kT(_4k}HVAF%8(02#*Yn_Uh%B^`l))L)d#U!+=Tm zo(MyR0)L75ySO;9(=pJ&jlcK6Ju!t>RaV}OIzQ%PyoT?0I2thxGgD<7?&A={3v45$ zGQ~skE#LVllVKyvVsN%|{-c|%NX7#sZ?%irmp%8@PH_(#=5)R`uA-|w2?f$0D#>E| zKwz)Dh(@G_41I@lu~S8B{Rwwe1%5H4K2OoIxw^UtF3!Qdhcu*)n%oUiAoNga zi&;WP6g`vVApO7jArAbs+wpL$1--SUZ8L~Y)jP>~0%}EVby-oc--D*fV*6uxDV>&W znl>Dc8ND z^qgwn+#Q2q#V4jc+bh?5`;+C?7mk~H01}8S19aaRW3?(1la4WuVS2o8GBz9LSkk?> z{9jneRaTzwPEMnkCNEpOdB|ZKjKrNVYN|fAf8KW}$u-*goMZ&wg4&v4=Yf_a zTt#Yn7Z9e|_Io#is;cII=Yly|Ap&p4$Kp-tes!sPlD1<@@Z|XTzVRg&CV+XCu zsQb9=q9ve673S~9V_$dsCJ`LZ&KM48@vZK8+X?NAPqz2T+9oA=?)wd2x0z|SeGyOH zF#_3%BsB%wyW0WYSrl4Uo;R2e;b|dt0^v!0&Ra$29tmCTLb)23$z|wHd40j0?rk61 z3dceA?mM|<*K=bvCu#FP4=wZ)1x5;9n5vw&ThqP^h-dywA-*`Rj&E01X?081S=OTb=9be5*2!63C~n=HEZ0QWO;$NJ*Xq<# zb%&z$)|1OYmaE58&0h>Uv`qy=cO2;Iy^|^@^r$WnaR!uVccADYBgP&#|Gjgqbr{$4 zyrda=MG;5k5&VL)&%6$I_9}70AsZjECB`KSL0b7Q)%FCn;9FUMM)h6h0MwWFhwclc zidExXpBjJE*HA}EoRpmA=tKd!^U!;EqCJb zfub)mSg>2p3tfp9!!wwWnWzFcv$RZ|{X0qX+U6Y5?=QzSt^2xDU)fl=KqKN|BywG+ z)-jGsqqzqY4sy5}OQz1s%37RRFrcZVad|`tT>~xWwxN-9yqB76^1lBkR{Z@A%f5E@ z@aVku-ujHy^<10lHmpB0^q=eIKUw#U(k81F#=ymtN-gj4)8xM5;^H+_YHj@~j&}~1 zOM+1K@}bStuG_*-Kw&3ustuWsQ#QM&Z+DvLmoo&!yW_!RJz8WeiBxI}<2Mn(YPaQl zhPP5@IgL(ac`?2NGp?srG7(0XqnG!Y?Dwc*t>z>~%SGrd% zpTj_qs9PUIR9~|O`cvtw)Sizzt5*L+w+D$woJof2fBB^7Tvtg24O4rb@O3QAxj4J7 zcnlMfmo!n@q*mzt;<$wuLI~LbUTj=`=Q_O<`j-&O$kj;3Aj-e= zjBr+I-q_*Yk=mb1IX=zIJ4k+3InuOXn9La53TC;?8qGGHI-V0O&mND0&2HiEK|fi# z1PwcqX%Wb$#NeD6IWCS5XVs>krKRX@%+yGOo!>=(QQf$|uR#M=w~S&Odu|)K*wskt zlH(K1Gqvt#Jg$3=;ZZ##SWGVq&bf}pAWowrK(^B)hXH34>GUbtSSAmUGI!nxX{Yz)qcP&m0t~!bmWyuxrcG6=A9Q)t-Y{=@ukaQRFfYnmkMZ^x6J%XGK7xXaceAAO z|3?ZKLj3x($@@p=@X~iUH5&VXfS!#`{5PscaW0FQ0=f$}N)6I@! zt%QuSv%~(Fd-|P$qJCjg$~Q@h!s61HFL$x>G09K?9`X*khjD}=);+$u-3zM4_OWfE z8vOqZS#}>NiBaJM&*3$z#f;oN3iiU6?lD2y^VLXa}s!qF5s zLbw;*#sK~pgLR7pnYEx0<(Dx&>9WVEXzc7|Q32w9|M5uvhGrkY7F*73Il$#!xA_AG zo!UBAg$$M`A`I{2@+8sWTSrd}BV@6-L(t7PF_8zLOi~7qYglg=Q`crBt{0O|67Dk` z%}$Le^bt{|I;z}hS44IrL`9BQYh`vPZ!(VN*gnI!F0Xs~qDN4L_PygwCtGd>VP~L))zK5o|Z z)^J?kH}2Ox<<^Aufl{f|GsK!m7Bc7k8T?50ITXX;PAv{mtiLcwLt4b1>ye;N$1_}` zECi^HDn(GR&iinY^VGc>O>%N=XsF!n76M(SIv+q9UtWWk3)ftlO-DMkq1|j0s7Ttl z7z9hMuU3+6iBR+|_zgr!1z+?7B0xcE%B`0v;>&&=)DCs`s20Z~{@EFik41s^ z?AHfFM?1yQ9`@u;9kD0boGyhJ{0Ldh=>;2?-wclf(FEjtV;@yH=4G`hik)iLi6bRh z4t|P;I7v$UOJJj=@^usU{S;SxG6ms>1bM&Dn^9ZM+xqBby#b5#mJ`I9mPa9RffrKW zC5B`~8GLvYXPeDObT>B*Tr0HExI%A~1)Stj06^c0es3xL;=7XRaE8`YHqN zdFvc2L8b>&Jy(?Id*k~)1QFOv7A{RXO#Y#ea5Y-tW-2(&gLF_3%P#6k-=HO?7pimf z?_#aL9Iti`wADC<@15N;>)&yJbaxk{TCHkwDpaEWTi;)GpB<8n=GcVtq>8YfN!GY5 zU-LQE5~a;34KPIOrXtZZZ6t7NNYMKCYM1^ywq@^!jgyRo^X3mpF0&8QR`{`Ocoj{b z9_oGw>P9rWnMu@gDEYPwV(>J6lSYlcuXos{DN)c0d43{LhQUr{zYz%iEY~vb3cRAY zxnI4Jqm!|XBh3Gm1-U)TWuF2Nb(96SFJLeskYkc!;#(55nR}*Cv=BbDXMQ{7?hPa2 z9|tSN%O|w#Q133eM?z)_FN9bjEtV9q@#<6!VfN`f5~xI|MgCyeH98 z_EQQ=>HhDHYa{e{hzNCS+h6uwcS;x@zn(ST4_|zYa9*@5#preP?OT(`rqMA8-!z>p zUanDC54Uy6wjaE|FsmKclKwno`D2POxqOa9+udvUssno7d_xqWpRwZy0{_)D=wYp} zEa~o&skE5CpwHsbDN3hc?A}yWbwgNlv}7!w#6Aw!zQy=Zyy=D9qz-^&_2UwNkg0}pjFC=ixx{F&s8m|qFmFCzc%LiT} z^#|7aFFE`PuJ3D!CG_zur5igK9|Kk6ikXDStQY%3d^Xa=;m`|?UL+1NxkvyZ(Ud22 zG?v)d-kjdT{1ZB_M0W2a@I1m=zy)11pS>wrvU^N)XOBg#_z?>@1 zymtYdTbd1+HLV%JTo=S9%x!>vvYYzg-q?^+7&AgqDmnPQSm>HWMWtoVzmh2423UpZ zh#jgGrrc4RwG$PhF8m+*OC*RtXJ`3(eWeztm$>0dv-)uy%l1D0e4%SRwPDDYv9@A6 z;UkZ`7!^@&k2@&U3?eq7KOPFjYFfMIOsKAMXoe~mZ_CeM#QghdDn5~aeoI23=YrWM z{Aq-OZVL{RX!SEUv*D^A?|pjGeSss}Iy8G%7**uQw%1^k<+Ik`o}Pf96$=9}jvpcE zUt$OKu_5mTQ+N@6C0VkR4%WsKg}{1VG{#nfB!@3o#iB(CauVntcT*or=dBmjtD7bn zJZHJz&deck_ljevL2@UxRsFVD5qKc%$p?ef!LpjSb8@cV^x!)rwI9&w=lF3i_4`P< z-qaOxWt3JQ-oha6G71<7A=H|2cPs#Pym!Z$H5G zbXyQ=vWhhZIk~*7ZbRa2JF5-;22w}glom+?PUnchRy72){fNOKH)sJ*j{cT#8I-=S z>^gSJ7(3AHh-$&{tEKAq#!9+rc^oFCgX)~NasI%y3W|#XIiQ|9D z8ca5u&2+zJKQ@RZVdg0uw~9Az6}#O+!;ImwGRS^~oF%7Tg!9I~o=iLN-^d=xbbB_x zK6{h3j@o;@Y6_{Ngnjrj6YYgW^Lvb!If-eOKw1Hqi+V2DC1Box+H_7^CR|0{7`B|;Z87b6cztQ`7fL(c9b-C{9Jb$plCR2mIE`N2mhJyu z0&_H{tRI%`r5AQSSSCO=fXQc*}Wp zV!*zf?{lK}tq|V)Y_y~>)ytrS3F|@ywmEW_0T{2FU=reXpp{J>$;A8m;uB}%E;!fu zG7|Sy$OM)X^AW4()<)=QwQrZ$3&ZWQDi4uSt44c`W{VvgUzX8~O{CR*CWv5~L_+=N z`Xwax&Ev^0NA3|JY($+dpWMk!v?KEI=W!Y-;q3^J^e#J!FFz9BuL7a=R-!$iuOmyG zLAaSi?OJyH3GStdaC0p3be{FM&ZFYbpc$ATBGZOi{ON%`S>nI)7z%ashb?M{b$H-? zCru9`H&uOlA&Ur>$8~^uM z%5T=Sej-LCgw-(0m>&T-{qyc>uggVL#5hQFXxadBlxLI4%@e+IEH!-c(+$rLi%rm) zn6nu%JZA#5;2jLpOmj%kq82(bVR4+<;CxRkQn|N>rqd;CRH50lG5;qIG?(b;pS^@Z zb&?!XSP^HfioV9S$Q2MzGpfB|wS=@Rf&_=M4G51bYr!ts`SW8paRw|>s&0Rc6E&+( zDs;n-r(lS>59Q--%N0D)vx9SUsr7RX-TpFSfn;K8-rVcEIpf{_vl~&(@>Gg^ke4$UFPlKDK;|Mzy6}lYzJ3V4u{ENa$UudWw zK?D)r0Sbj4-V-xXXE8OU-ybavKs!g9o1NSv$aQ?YGG_q;k;o+qWMk3R7)&sQRdU02Q?*Yw2O3$dP1+Si258ipW|cJcjnJ3WC#m?M@Bv%Fe(=8JMk7nXFu@1* zaJJ=-3E&5QEHK~HFiZHW;1NH9!c?9hg^4xyq%=FoWQjFyaTaw-hObM~t7-RsoGp{W z+mtD@4(oiqvTUF#GNR~t$5?Nt|7(c{L-pj}3t9(dluRtLyjTg+@ri1XG_G)ZYaC6J z=2$hf4)5@IExqxioEwC4ztJ?+kJEQU6(MIsk8tBzh2a@jenA2(0rG@aEDNL z9v88h*?TEuNwLfu3i!Hzef!~TYfgOaI%(Fu^BQd}UE8jL3d1CRlF^O+v+KWeS-Y@t zJXK$X#NotC6K0`)!+n^r^_kn9gH)^bnBQgOP2qa%+qsUm^+?>Uy(*2y9b_1}#E08) z_eg%B`glWW2Q6r;R2vgv#kHAjFOzSg4lF5o0udQq+f@NpOeQ=@HR@w^vk% zSRJn_7O8=uL$@GoHO&V^U-rf?j<(zwM0^pwtLNZZJIl`a7(RY~48v*DPoGHLDOAY7 z8&d1i#avXo=?4+VD5gFp#Q!#zin1J$}jEo~!hf*K-z_~O>C(O=7^z8?bklRq_l z7XHl{TjL=11xe&cv*j#bCv~y&eGQ|%bai%qa!C7$#o87ZiumP~tDUP7L>{si?VVd+ zrZyIghW$Aq6a7Bep&&M~2!OkJ;lv?XTb?E-C|e}^fP%%|9!oXq;w6I5oi0t3-|G|b z*_nL9cNXO#3UUxxnO|Gll|+ouG0s6sp>=Y0X}=Wc;KdONqhA0?jB(CzC^JBd%m^|Y zB;-K=egmzy#P(lQ*>MYH4ft21N=lE87-NYBeSfObtz<8S9{h2Z1vV`}fa;p#)rR72 zV-S(cS1o7$g=Ee~1saq-1>fnSFx}MNwSJgwS)u|kx?p)5ajjc{JYayPL?*=}KNev5 zSeeUnnB<}cM0viyiuGN;S8tnN>t7{XS~X79F###xUrwd;o)adrxLjdSjWX2{*+qgP z+^37+c=zC62I)Xf$CWb_KOG*=x{O>c&8;a!$IlPtUe4RAX*LXNt+l&nSVx~>%E|BJ zJnkK{b>LUSWx2F#f49=8xAZjGtXh!&GLu@EeWqP=Ojp`Vzb)xGI7XjJS)sX~e=h!O zrG2oSso0>uEy#!1rsfGR(mVw!0wcUtTU5sS;UvCNWB#mLcV7YMlH#h@g|bMQwcnP5 zTo4AkUV$Hq7J1n&ubzS~pm25nVhW z4FsGn^B@8L54J!_zoKv;-YpU)gC~02*cdz$41Wz1gtk;*| zj1GMIf!a50fyQ>dFZ#=s=bk+LDGnz&&t3EuGpmuhoOS0&la4677OShZ!qq}sd+tg~@pKOk0T zIumf-KH&}Ie+NVj9;~6QmFeYNDQ75o5Kc5HmyqLBgkcm`TmX~MMM|fsFa#yOLVH?| zyWQG5+N8v2v##zA+ot7DPs!?r&4c}GwS<;FDM!LSGbl@>2+=aBXi(Ka@YU5=K~p7Wt(h2y*jg&9Rn+ zEm8zEIGq{^RqT3?u!5tQIGPu;Tdr;?Lz(cBb0H8cbRXy=NQzq&V8W-G<7-uhXkp`t zvib@4BrM$d4cSH|Sqv$Sk}(zoa|8k>g@9>s^Yfqo+y(u2z3W}J=Q4zRgzze3>BxEK zooDZU*SqbuTW_`J=@|K$?7Uy7AD*eDSZAGimXCE*y|+Q{lBKN#tZ2h1Ccb&)Hw(~t zU!s$%lQAyhqSAVaj=8_;+gJHG5Zga-P~y;cL)_3i_up@SDZ;Pw_8Z3mj*>=!K)(%3 zp*)H0u8qP`pt<5keGHx%SLjppeP8}0KToH+Z=F3`e0;|hv~GE$xP?|vnKed4~H^e#lIEuaO)b;-LB=E@cQMieA)A5d_Y+XFF4vA{6HHX zDfidE_VsX2T*8Q`aFoX=SE3{B`RAW+Z+g=kl`c%F;Vq7zaa@V28NK29>+Rx;FZM5&%p#eD@?&z# z`&`{@ait)U$}*DJ>cte<{k;7`r4QmW(slr26};Lq!_#dT|PcL zKb{}LTlUj)xiTdqSMfsfQu-C6J!GDRShSo~bT*W{FYlpf(! zq@ak|u1*t{{HPseiUX|MP&#Ly4MoJMkR&CY*Loe`JjBk(Et@lp*yEbt*_8>563069L5z ze&mXk4ZJ}&^6zC1j393ljc`M!pds7^{>cp^L1#|tQmkgiSMz{{E$WEt7t0F}HVi^{-ipisA;H@~$NB?w9 z2<(aopmwHv(S7)!#**pko<3`pp8BqE{%VTxl_R<<;zs4g~UTbAR$mdz$fFHh_d|U z<{$sq-g41JHgDd%LU_DkvP#@8Ef=}*h8yfwEirn&zLGa-uNN#V3X@1rP&!%iiE<<~ z?XH8pJW9(rRIvucbb%(Oi1b6OlDK(cB1X%SfoahN0V%xba{gs$Li}8~JW8L66!Ukb zdF4ZNPabL)#dmBOMTL75Po}UeIA63fis3O!R}33SH-_<59TrCHMwO^?E<<8eImv|j zvb^Zn}^xX(4|sN<5y&As+FI59RlycZHdv_6nX^vvd~94 ziKtG)_rmHXd+5i%vHJU;v?G@8Ym0id$-8T5ilyQ>X*Sp0qVay!L#xa>du)~zFO7=F zXINyzj}*C(fn6Vu#!%Psb2?eTK=E53t`Ip>|5#W_IS|@~g z!R3+O8FHgyW9V8Qe|Cn>zjBdAlct=~SkNe^Irh(MpIB>09eI=p=V~Hj9pUo8l?U3d zwQ=@H9ev%K-bfQp(fB2`nIFi+Zxp_rbuafoEJLw!{=(Z89t1$VB!dv!|7JhoWxe}g_L*++DACnT&+r4fF%Q{9+*Mz_{M}VGCuP&fH zbgf!r`JC;wcKg*g*rJCw*-^VKxA|H()DXK!>^8b~s;ji=6?6Xs4_o)fExx&KgRD7g zG}f^n-gf%0?)gB$67&9kDPShqCFRQ`S3t@>Au!Vsh^i1KuuQl)M)|wn{jR;`!VCTA zT?Wl4WQ3~*Qw_}W_R}KR^5lj4~Uaw`8WjzE~1$9^&$3d>$W3l_|; zEK_JgwPN`S`;nFs`Z5+3h_VGaB+j}*Db>rTLvbgZ$2hJImT_z=QMMZ_UWshzk-$5v z11^O}#%UtxPgW&<(H~ce=o;%Pj?G@DBpI#~X+JL6GmD@{Hi>WB+C8c+u2 zI$=KGdG*!bQ+_$dwb3xoDBL)Xwoyy3n%TppqswZggducj2lzVzwi1OMwT`SdOD?3r zL*kj&Ymo*D!07UAJ@{orVuT5~7WtWw>~_gaCZpb_9+0usg9i7tvL4>{n?Kuv_1o;o z1$)ZkrKY&Y3+yXm4_#Oq)NT4baPNb*?a2++%ASDgsgOVsY8z>Sa@tpcWF(S2_Y?Y|MN6I{^ne(i-U zEmGFsqkvr+ID?klVO$PvFeu1M>O$lzjpL=nnQ-UE zdeY>dv&i1Q6~>jRwLj+wMIP~@=unJt^cbEMfHe+Fk)rc>*)#Eo8249=ak!S!hX*3& zV^p|zf))HTuTVv~NV<(YhbSSeq$+dK3zXfjG-V-d2Hm`0r=;Z(1Qyv|xgXXpE!yL2 zp7xNq`jh`p%NSPJJSk@C^i90j8YUjBf?-Wb);W&6nQ4Eel15MxxpIRoi!!#4VyZIZ zW1G!PpjK(HnF;02m65&r@{Y}R_uf9=TITI$Exr9Z?MM;gSgTtUFI&~E+jPDEulHH! z=502wUWYDpX}>TLA}S;qvzbpI9q`MK`+A%-H%%>Hd?Zr8*9$Z^7{z*-G&EDj34w&b zh!EgQAJ!^d**y1(E3U8$baDh=Hw&8&W__FqJYP^bDd$JpP!?y%=Q?|Ig#BZL_T zpyNteA99OqFfJ4Z;6eKBmC9qta*Dg}Q;Y@Gm0Ilh;32HyO|J z%@reW<>&)^WQ}q<&w8(Lo27RzJci@QhsnOXh>cT|Pkau12Yhv(y%)H63}sl1385^# z{6o}8U)S*8X?xj;J_1)lFOn5eZVSaI_>X{aj5^%3r3H?uM}PHvhmpDR1#Q{{%dKeN zp^XO&s`O!|wyMS&dKB@Ywf5t$e9ul=yxf-cG+MI+JC+~S>KD}=x~<^gV;HNt339}UNW^4O9b z+j@bweBG4PfdUv$t58a5&hPPZeWF)=yhCkoo75?*9$jM{ZSB^q<)01RI-ySk-geC^ zs@XhUG@>G=UvfiL9UH^5oVCZ?7_JPU^5$F^JImsY#pCo;zIY{s3DsDDkZ2?X5&{#C z0Fxs&1;py5p|L@x!dKh--}k<-)JW{$A1AbQd5$P1%};;w6T4j&FE4uG3q3qev{1~H zOvtk$5gsSB`N4j)C62U4n|`{b1N-R0BtJe4aaEdt^;jT2L_QN0G@XkvMR{-1UQTN? zajN7{#;#kx&N!BL#}+&)VAe+I>+UEszu0yQ_qySJNVjYB@Hlo-)N(a|7e9wXgvsM& z{_)-&X?l|!jj*-W1C^kd; z<^xQ?H`F_b3TKTa^}1`)2Ap*pw%PBmyv|PCbAMahU#sJ5C6GzNqBc;#cqL@X^i<}? zUid7_p!f(61Kc2t!F*YB@}$`Y(gVnhnMVYS{K0tyge#Vh^b?5j2-qFpsFZoSC}74H ziBjrW`=VXNrmy>6o!qunowc*B${xDsA?w`MW_41a)~dZn1uFZz#0FVtmf6@4LQu(+ zx?+l=|DesZ7-iIV(^L7P;>MTis7sy_CIn_80&zw-R%IWyG(IpxU%RhSwY|OFuDa?f zyGW-RA&}9xQM|Zmc)Zt%87Nk+m&MDU|NLh=O{W^6Y>5gQ(y&QqWjCHfXyqJ}Z$cm; zFf;-S<}I+h@BT|MQofsNCUoDu_u0bT7HUpkm_BK=$VVzB^}8&HA!)3Kxw4#Q)?*IL zh=06}4G^lZ@=uN;%$QEiQ?11oP|Pv%;B&!3-4ioQ75y?A#gsdDmxIFz!AHK0?i}?o zPMu*;q&gUy#TYwvS6)#A^4}@ygC=&Rn>s#qL=}YU%kejz1uRI zBc)57zLNV%Rf-#yH)hNaS_T5j1IdU&jzK~5q&3$Wk;|tgVR<7R5wRUaF3Br<7|}d% z2&{(7cL@(W;CG1ZN>Yx5W=X;+`Y@K|aSVqGp`aDdpEWLW84UCRAY+iHROdC@<;;jOMO5>^3uag^1i%Z@+G{4}##Of5BuiE=`~ClC?(E)O?XFhRuB4Uk*sD8t=FFKhXZpEk&J-78;Y9Uf zZkHgCJ<=$vSHB|5&RT{^*(n_7cD~qm8|-Y{Bl|zWI1hU+k^|sM5A>;1#zUtbjqrkX z70St0u?^8@qU)(H72hQwk|BM2CvleGzF|r{HS%+38MdyCv50lUnADcLiD%nmNH>wG z%ntdmd2kG{?TK0QOLz{B9QpUw$?Ly=5Q3NSGPW}Z#&!d$4I{As!kbV!v3|&k_3_4C zh8{DZmkA;+-inDu)pZyfK_q#dXn%=`+(r<$tMXjlF8N~L)vZg0l1CT~-R77WKq_!1 zmNTFd;9J7D9Kd9w90>ks8%D-xSMB}`2wq@>7k5!_*|JsI8XELE=^Pl)mOzIJSG35s z-Q$JbgTtYlbUQxul%2_jPfOv;yt(sa-rV`&ya0o3pXOcOA8g(%@4x@P-13#LNI^kC z5SEpo7374tDA#S{~&=LaGHS*GL?vq8O<76~$Ov!8OfZzq1C0QT{f*BudMc^a@HV}3P z0l#fP{adXec62=z5sC}*IfO>*FNp|3!f?fSArGSAC%6sQr)63UJT}kbI%jMKT9`g z6xwvqs1}~xIid>d*L-o|kC~oyXk_>p5fi?z5&e`+G%~~Z6Ad64VJKf@5w(^>RUJ2% zexWfg1%o>>lDB=QHu)IcG@UemzG}Su>Fz(t_zB}>MCAy{LjT^reVhCbZafe()FBUvVN)WL;x}Av%!*HbL-UI?vNL4)liK z?rL`X`$kFp)TGnNi&|;zkghaA7)im))@|D*fa~E4Ab6qiY$(A{sX8?|sloY7m5x3G`aQoXRD z$^kn_^|gH3o1%BA9-$7S$LZ7U?H#gW#R}DUS&pMdoUEzol7)V)gKe)mEtg#8XhS># z4LwOHSiDRINt|J0|CE*UYOh_d>buvurBy<+SxhUErKGe}e*EJf%V$6PS^3nbKPAT= zd#v`&4eK{Zc8*_e`}u!MVPT>6d0Vpn)-bU7rp@7`B*Dyn z@EMPJ^21IN>0^G~x@c`AT39?pS_WGX7oZK|ljX8yIE8i@;>>a(bFe{1DH4(ANyLqzNU|4M>HA<u36 zfddES2jBmJej(>qaVog+_<|bubT3TofaVSZth>TuwT&p*dqExt(u)JO{r8sYIQ^zK zDEU>z3Sc_$RjLz5kErqT`WvsS-G@vFTvS^H`SMC?3)`E#Vy1D*WR@8*p}Yu(1ED(O zNa-FqJr;Gx0dmVdzeK)wmwrgZg8@^fOp#yw;urGhBah0~54YktVL;AZey&`2;e|4N z+B98QY18Vid4}uy~15#sPP~?PEQ$uDS8bg(Ttd)?Kph zj}OX8rDLR`BU=ig1;ecH!Q8APoC4AG5wqpfntb7&LE8<4I2nCiCpr1VdXU~or!-;x zlb9pnC>zTKM|3QsmIw4x7{y~5bS#P~zj3Ak1xJ4M z)4xb#vtW`kY(2{ z47Tgn$q#?{Ln8&lNJ^DL{&?(&F6xF&DVeeE|CxNuUZ zkd!!Ig|W>yaGmtIXP;}18lev}j}Az=xfq}@h6Z=zx?tZn)VXPN`-#JrgMM9H(9_S( zOb-2=DY8>GC9r}ZkGMIenrB<9x%Qj(IcT59p9NbztRWza`zO@F1Rj z$M#{<4w1dKX{XV2X*=Xrm%uJGcmaLmKwsN*Lb*q&Mm8Ddt+lN2XcbZ562hM(AXo25 z62>8h29?#@_QJa53W2s4(Y)XP>@2$wAha7#xoQ9BI^1Cg)fYc zfruMe(_aA4-X{K>96(#7z-Nd;SahfPTOy)^L=ClLcGSou1hNDPENyy}le_KoC}A1{ zYrAyXNN@Wni|?*)_AFJ3aQI^5*wm=?(hMWIjktj0y>;s)v$;)5vT`H~!WJAF6kk>b z>_JlKg7v7=P6Mk)1Ia6nr&Ox3ha9}0TUC2`<-D)bZM>m zLMHnlWZ8yuVAa*t^6hVZ3;crWAvOdyj_<5gO3BE{$c7Wf>?AT)^GYf{51Ge-q050R zY(#mai0;wH9+UMrI(XKZOC^(=QXTZMK!Tt(6UOlrV3QCiNi)IA1h zj1x~7&&B3|<(m%T+`M#dKxOp?6>l~g){!B9`p%DJrVNwzcX!J^*oDZ(+TXN8)7SN{Ej!Y5 zVk+b3S#GK+sNl@zgP?{V8dN9~v+;!Hz_^9#Riyb~>`rP)c88gn!|ZVw&P`;i-zNVo@&;fXpT$pnqW0|uE)7l{ge%u7r(jj@cO|rG64Z|Y@I1u94xjzgC(nY#N zYA)*hABm`&_b6FAb7Ea3lrf%jisBp8E3ClbjK-?2{tc5``1_#!C)B%eT zp7S`+j~qx=dyYynSGIj|NKfGkT`M$M)~#JDYu3CW=fb!yg{Z+*GqO=DAnUwDyVTApuhYHCvG{JeZMGEQwEt`Qlh@y$&bAAORSmlyTOWkX|QSo#FB zmbc%2Oa6S%Ju+wZY`OA^t2FPgfBoy4{9kXl0ftj2zz>e=e;4N+ZyOeLw%)jiA+1`k z@aq~EaJl&ErpL?@B`OW!I5&OwyWhpSqFrwKx0__-$dS^9_Rr(9p6ek8qOh#HVTnn? zmue=YhdF!T9tRTUK$K>ha9W-A5{6E5Ky+T^6$+i`MwcIHa`5dYzqv!o>pEq8VLq;u z_T#I#If7v4e2dp)VEDunK31E7n?W_=2^zR632=Q9YZn_cqIdjfHmqlUz3Q%k)9@_>-e2P~ba@xmYnr=8OY6VKUthzVn|i5blZSJ_FrGnCi8MGvDIA}>&XCTHH&r1uuwfqz;!aQ(DkDbHQWPaItOA(&m^J(W6IT zjy*bPqxFe2?BD1j4g*~k6$aS9FIkCpn*Jp#er}|hDufieRPU{p&wuXo*kFLK2qS}8 zKR(nd2Msn>-Ix$1B4Y_$+&Ko@M`T62fg=4{v*Cf=4m9E5-kZmPwBrCb&%Jf*UPfqu zzKTI~<%$*Z*4uB(si&N({Zy}xif zBXaQILHQQ6{Ei$sB0u`kk7V?y(Pp6to{)xh>(UdLWLlSl(?q0R$jr8u>Aad@Y9U3t6yBLLW8S6dbLcL zKp~I`*M)P4{TP2m{{8F!j@xXuo2<}u>=!v77m-l&=D+K&zh2H-dR8Ljq%91O)U0}b zmGa~>pZ<)D2#hdgar3JurNev|h_@Q+39eqiYMtA@$}+%@X3)j;u8$CL92X@c2B9UI zKCkrPk3NBiPsfEPwfVRJEZ`t}Dusie%+zwtDSza+sdb7!X8bXA$}qljoEbRRhrGiZ z>cADDH#uuv^d@2AX*h4tJ_|Wqh)?Hbtv(r!U7Q~$7a10|al}5s-F2cj2?G-j8A*>> z`zZd%s5tMD8%_(#%_uS2um60KHVe;utYaA8sqI7I3FScCv^HdQ%E8P|8FSWR@t-oA zJ62=+LKC}6&3EEp5Hoe2zBf0^f%~76!e-GMWuR|>NF&tYlW$Bzx@^C*?~L!>u@A<9 za(#R-Id_F+@@OcOwyxk^LZYR$K;e0UF#u*gv_MT^Kh~bv^Jd88t1r^>XB@1Ty8rsE z6hi=$4N#VgtR$28)cP~8T2Jwhe`!jl4mngR+XNlyA_R})@M+sD^XTfJYycpe*2dd% z^J&xDxI~`O^GYOT2*&b&A>@}O-?ju9ZsbEDYI!VM;dnb4osQzc`ZsG$z+&A? z8!s#y^B^pTOg=4VnNm{|$Z<@sqXxo^FBHl2p*Nc%dC#=eeBnU4XBSiotM|!`H{GaT z$c^ed*hglPY5e16oxb;sai(IrPs56LUyW;1q_W~{9^TXAK)P~32Rn$WTUuIVH8%9G zy!xtKaQ^wK3Bkpbojt5j)jWBGFoqgzx-z1Nbm!*gNFy{?cJJOTYuBxnF=NLl&nO7v zIq$dLS}(hIRw>pup-HrG;X?4ZRo{6$X!XmhrD}H-v|@54FF#KW7?Y<-cbN59$JS+_ zu6OO+B?tB&fObo+3UGqL7tHKBW+M$XKoWDrZKmYz;d=txJ+{6>fF6f*KHPljJ8lny z;Q&Vw!`YB85#A))rV_!G8W^w5p!;ya-HUZyYHGy88j}NDA2Skl6`BoVLiZ|y_&I~w zC-z}RqXQYz55Ru2H=UapB6M%493=zjMLpX0&#l4kDFxjJ2P=rH#Ywc90fc~9mIflnW&NMr_k~L*mUusTxEy=j7)FeDb!N!+}te5tSpzpInzKi)}Xs< zGZXutr_8;qSS=oKVB@3U>R+m8qp=x#yV(NaUoN(Y{%5Hu{gtk!*Vk*k=PS$ z&*6AOZ@m%NgiYQfjQTn{xvK}eJA5KpxGO4pBae>7osJOhwCZQ8@|?$k z0pLI)+Fm^+D_U#j#=fVn6NBrf4>qas+-1v_nFY10_i%Bo+A)q+TTt+F6!)k6f&v*g zVVvyTzEhrk=2mIhLel$43Fg)#mPD}RpQ-AOCiihuf9soKmP)y zHw0r^RY^%{2(98UbTdD_>55;+02-M+@zZt?v+2ZFDod)_q+wkNBW;!j)vB_&0FXHo z&gQ}Lv)>Shel@ko0LM0~%!Z%%_+yy}r+ERJDHE|0Mjr}i@*sWul4CZHjOW}HSss(B z`@bd~crt*Fs;C~`#5fSGkJ0p?(*Bj?tnFBsWVEr- zf%!U0f#I%h85=&8F$oaBX(nNp0KSBA5s<@aj}nA{J9%WukF7eFWB7G`P$j#cdQozk zTBQ@VGTa}ENj8*aG_Oo-mWmEFN(Ig-d3s2#y}9!<9O%uuDQ=0kt)lZBVlVeogOyS|%xe+YhvXF>$NB1yEadL2H@cQ{jtEr}z?_mDwTO3(tO^)Z9dSevbqF!2zdlxCgyJFU~+` zV)EL$b*o%_@x>}YpnWFJ%bY+d%b}kdFLa!|RS4kDG)`FwMlzMu$k97aI!3WiJ4?kb zkmj34mtYdD>7v9WHHhb1&piE1dHKbcg@Wj&#wPI(^XoAq+JYfJDU9JKgPpjdkH?J4 zOUpq+KpLAG<*jvZDLwP&&xhaz0vGU?Lao}vwX$>PPNdm^qfLi!3(Zs+3$2>5W5*f# z!N@SyhrMf!sepmN%5!>jaHI#0b-{nA*rH*>WO(Ip7511&^Vjm+2qR2l3X}Hk-2>Wo z$?)OB)jrD9W2WLL70QSVRFHwTK|xbXOS4RzI8kyTR62C%kd~up z9}L1c9F}ji7Ze+K$fusAi-s#&SmK*LR<03N+4;{t-8gJmw7hy7Mx6WyMkoq>ho47G z7!;nTf$42tgDG8jxdszu{YQ(of7|xWaG~|hdgmsSwQc%{srP8Lf7J4kOd3e1*(Zkv z9RY|6KOJvS=^Pf#$Y91`r+I`;N!=Ac5$Y`7?r`-qz9)I>IH%=BE_`zF)Q-|)Yt+7d zuT=l>aTyIWZ~lli8g037gUdwR=EK%5v|jL$0ytouO>t=~ysAOO7dXOpVohb2Rk3Jg z-@||HhZIoI{!A-rLPGA%C_HI}Kqw1qkX+dDvl=$cLldb7ULt|xgL${%h%)K((UuJY z^}~mfi$7on;hZG!g`5?RSizcwAx@UTw3!gRUjy-u1nq>tdX@W3I?~T_@Ji$s5RPI= zHllbG%WT2%uyBY_Xy1G$ z@4n%POdW}n#Wz3v#eZ;HpEM-x9t@%~)rs?h@A@0W>x|civo#e|~9(uz>=TV~;&nX==v}2i1G3W$H0gbOPGE z=>s`Ze+0spGRc582ao;GzK$P)i#fCBIQ$me7`F}fWB#vv2q8gBbBhjNT+kE^E0P;; zyira+{d7&Id~pqJaXF>qVu+LG>R+Hnde$qyR+(Gq!nqdO3_|zVhWSLl{%Zc~*}qeg6mFm%UYc<-50jS5~fEDX-!fTXR#hTz|v$a?aW3 z$Sq&KMaZ8eOBUnk)hoEo!5qcPg6iI$@Z<=6(7UND<0vx@!xKgvzDEufxp%DcbRuH0VK;R%P6x=Ys znv4#~9aS#nz(M2u2Y2t4rUSK-fg@AQKOaA8Pmm^-qlsmMKM&yrqz%su(1#!uYN+-K z1UmI70P|ouh@%i?rjVr(aW!B>F122mh(Cl?V;XPD3rG%-Q-G9*GL&Fhk&C>k&BOeg zQT7JV+z2|_^0H+3^hr`YqEcEK8f5>6JEdvQLF9wHkTwSiki80Fm_?)uh1pt&gfQ=f z>q+~KjwL)aevaX=iM)l%5)RFLwC({wKl#tHkS}z~ALCU3!V*`q>9bj+>Y1j(7k;Uw zdO0PCc5~J@7O9{m5=El53AjMmp;Ch=a`2Bt@4b5*=uZxq=A3AwPK>gowQ*g5aR{Yj zpLPT<7*@HMuoJKuW(`y-{<1ulidgs=-#t{U)IAGl*&`h{`X$9B(AGFs-dg{by!-Au za@_I9!N^*My!hgaIKtH~r=M}U8YAEI{wCZ|^tMbGKVE76aNCE{ifg)0Jn=-;dLdsA z95^61-F%ZAt~o4APFW%g7cGERNsc`K{PXg{^DoG4Kf6tiKmK?%%xh_3I(3E_VH004 z4gj%)REJ@>cQ1^k(-EHkh>8NYF=NK+`!i2JBftOs@AX*eMVDNxS|x{IZ1`z}uX%lq z{LlaXuYBj*-!Vs*z$a?`&|b`UzV#h_QW!FN^e8DFR;+m2+S^p9^72bBqdYTJv!x1} zD{s8^hCKP?lk)iEk4t$)xhy|#Id0x+g#qZ5vI)Yg`~G^LeDtc1Dy{YP^>WiqH-Wc% zl~)TEEPx;@Ti$)|U3ucMC*-HM{Zyt+n+C(f;5TSm^}-8s&)xTE+EY$BMW|&%I;eH? z#;b2=_>lM$(M&^aAR;0ag^kF9ebe3UqV=IvkkQ{b)1h3UcF^D4Nj$&);$u4SB5|%I z2U5E(=XatD{Ye^ft=r@-d6Gk0cUTgoIb!Tnw03UhE)~00A2ekH_~?MoS(qy!QGjDI zZu2=#@)HEJqp}PIFh?M@$iH%_jJ)_P3FJY9(A*}CPrWL8|L~|(Hd7db5f%w}`_EoT zNir7BkV*e~jRbOW^tqu;+W+yQG~D~NWVHrV+oJ(6wRxD^Pn#u^E?g?v$HQhyJ|r=y z_k83x0pEIL??E~6;Ip#-;Z;&}uu+O2y2yaAh?+90!G<+YEexL2j~pgN=P!|C&Rrrh zW;g^OW(zxt1*mV4&i6l*s;6I-&eiWoL)9T^^W!-1apPtDzu$nPSSay3+hykue=X(B z;D(h>fEjHl%fW&SnedD6h_5(Te09yT>kfSMdF37DP4n>+<&=N_v;;;LO8`O_ssUw; zEtOM#dn*`$%z1PO^!g4q$-(dcN;L&v{HvnKeep7?*jvb1rgoQ7NqTdXrB zHynrzbAj2Yw0ffDx{IAAv}17LBAs8t&7z%~vjL_(O8)Kz-~dzSW$$qyB^>BW8`nMy zp{2cQngmjy>cjPNd~$N;QKJsEkI{4TZI{$B!oI{oV;A8GfX2a+eMWPU>F9um$EE&u|6f%w^y@o+Ege{#eDagm%C*;CtK&U|B8wI; zme2j`=VZk*E9BBkE|r-Oq@bB-zRbG~=g7ZwSwr|@m6DLlTAN`lPJ<8gadGd)T~ z{zn;UoHn51T5j?B$bV58Iyk7mmfk zg@<;1OBAka$|Qt@nrt`744^4*2$cMwoe~OPc%&7xA2dL)mU4FFP{x4p!AKuE$S1xd zIlm!y;NOO)o|T0co+1IiqhY^Y#D$J7IrGA-v{ngTO+(_|J ziu3Lc+4A&rQVxyNENIsrhTvsyp--lM>QX7V{9N(n;+Qitg7b0zhJ4j9@RTws|IB4F zY}PdS;QPOnii0guigh55`m}@C+Wbs8G_FjJ|K=CPf803WK)ES*Ap$x+${^JE$aEPq zWumnB|1O6gdQJsISrvuSIlh7xT&45S5!v6~EJYnzl7kQUYR(p@IzZ=PXr*+HE|-jA zzXYI)Qir4588D_x4!0J7?onkDC<77*S?B_kp$sn;DD&v?E{6H?nxr)kFc9=KK}fX+ znlWV;ERr$*=NhQC@Pi^@@kg1B1iZEy7at7!r}Lz6-YnVsAHS5W&HE(_S}=OlhDalw zsq)Csl)qEpmMUk9q=Fan2pQSzte;Q9@yV7;7HS;-&>>Ra@z=uHrv)+b1a?9+z>E^A znQhw>LFkAeMJ`P>U%(S|z5peSspL+InC_wVIN)&r9Ecd$xPj(ol#2y!ASq_6rLO zg_Aax@SFEP>-mxWjbZ!4tu z2I~bpuCA=%0t8M6Dfdt@AR zUwC}VVC`KyNXJyDhNzBwup`1y;Yr#GU!vBK7wB=o<3L(*Ah;nS)fipvCXSnM<9BAq zl$Rd`7{ zmQYw?U1epZ@q+FUe)#zgr`&_^o`!lUILXe!s?3Q~4_1;im|I+euUgy=HuYBbz%Igl;t+AhB^u{C5w`<2PJt{?8JS;;=Wr4{UYEk<(15{C zF=zg9GUoE7QuT+&#D@J24G=V9tVHx>@UJpJv;S`p%W8FX{6usJ*Q9OGDrxql@j{w$Ena%Bz54$gI z@9mWJ|M@@SJLN=~_m$7+(W3x_EbF1EQdoUNhT)?S78H6FbPn=vz-t4JNGo26VzEyJDUgun&f1O6jr%GVP0>&?8=j=bR$G6|1Fg?RLqAfQYtd z28&YxTQZ43*;p+{tD_(^?Hozw!qsln;uRnbix;mrUYy5)A;kgqVeQZOL!S=D-0)%| zbN5}hKVb&g!Oh7dS~_H<&VojR8PKrE*-05;gaef@+E!6n0nLmmdF{2=q^9POZt7>x znWNf6ovod6^2r~OHEY)Btw$74(B2C*V@6Ow0o*l*U|g2hPl6Fsr%jc-JRHtup2$aq zAyidH{KSb9jbOx8D_V1|ilN;sTZg7rBRB_G9kcDznR*#!HpE7&hgvr^wKYn^D5yO% zOS>2+MhZWQii$M+2n_a7=n*y-kS5l-i+65%2uq;})2Yyg@Ihy^IGD5pi!aLy8tQGr zO;L@_STy3w>hZAYlAo8aMvvQ}y+S+`0uk@jDN~iUD%{FPH)_Nv)gmHIPLY*9WhFAQ zv<3N~se1Our0rRnWI;RZ20E04WegPzq%J*O(B((my`iBKs$|fy^a&B{kzo?6TPTTWehV zHReVL@7X3eYS^b6Lu7k#$GQFP`NJI^)z8cZ!a3tBTep^1=V0Xq-th?m!U`WWSbfvR zcA?IePlXK>z~Hz{M|qL>s%j)3Q{V7rpKSciy>cvs9{I;kf#yht6kT|R)UV$lwJ>nI z_|qSSofo8owoK#uTV%(dAC@sVdPGCJwDWQZHd;n4KTQHfkVIgPYzU(j0k?D))N+&dTd`+Fw z_|kgW{*&Lyq&nE9!Szg8zAPL~%9M&go9z18pQQ*IFnLXFFyIR#xiHL&i?`7Z_%m%H z6B;eNWs`yp%APnUJKvZHsV6GPEPi;(#u@>#CE$RO-Xl1?g|(34LVN(Wa7u9mtGqo^ z@|!>h)&Ol-1GFPe6Utw)a4z_Rb4So5>Zq=jO}GC+Dsh~u2)qVsBoFI|%)=e>;S(=P z+36=k1Ljx=sj_9<@-wCGy+29k5nAbS+(5N2n?IhZ7&;@1rFDdiOkJ@yjN{Y#ogjU8 zhig3k*s##k;0e*scc;JDcENt+_$_`Gm~^DdQI42)j{MMNzU5|+NnmMcG!B)EVX5R| zC@0V7alqq1UpV0O-&kXw>GI}pXlM@4FbD&W84nl|A7eO%bgCmE&F}kzxYR z&B@iPkaun0B~QRE$!HukqK%VLxW-t86_`*aPnn{Nh)tU}sZm%8t>?|1Cpkz<-c$_7 zS!g(he;Kz2@p|bnpq(E(R#OYaItoOnX;O@-rV zt!s|qz|G@O6uR*E4uvojd{BcWo(tu`slY6txUyk>c5Wby$;Y%CtL>sxonv<YNvt!%I6Wg|(bd1S)XU&KC4OOe^#@^Sao*3m3=oPf-x=k&%^FF{~ zw!P$}3)+X!rH$JJ zNC($rYi&B6QaKc3kIhj80uEA|;wI zR%j6FwyUI#rOb9kA~w$=D3Ed4dwclTzC5o4&9&e_X$!BjfnKfcJZ+T0dn^kBB^=k#gE>$C z3yBi{i^K+;KIhIY9)2hjR0HZ*XxunUXfG7xjmHFX9HfazUt$CsJY@1RuE_K1LD_Eu z%w7BQ3gnT0R<3GNz)BnV15POZ>?cvZPTtBjv#*|0u$n>61uZZW0a={V^^7(Pev7)atk@gYNLAumDb za$rVFfGCL3uy;LkwQ@#Sm6S26c{M|HA}3hZ5tM3uK8AJ7W$>U`dNhr=dx=>0n8R$wHZ4mzXu?8AQ3 zl^?=V*d3fh3#RaUt_3nbqAts{5@mt8jINcgc5#R30UL4Df%IWF44E1rQ%l^jX6Mrd z7(YpZ{qp{O&>Q!Z)>Qa_Xrk_6v0=}gfA5TrGJ8=QRPuH1WWQkIM`H1cK1{JJ3&xby zAcA~!;QK5-fn%e<<_*5v@8;w^fBj(qhWu-k!*hf{q8J3)00978hY%fX&F&JPjwTcx zj{mKRZ*M#a@H3<;>=6~gL#yi)Ec@yNy^!u&HkpUIh9wgbpxa11f$#_2W~LCa>46%MX14x?kivdsxqWeQ+|y z=t~kPZUaPsW=;`0k>^19(^4ckTtbywMFZ2z9(33@p7wFnDSR zOvDcY>(J29ltPWfJ6-po;K*w~3cWMgMKW%WaQFf3K-` z%j0`pDsG}X$5?hDL4q^jY;Wh^z+%u34TJ;2^C_-4H&QvVv^SrymfKcmSJ5?Q4YZUI z2F`>h#CN5Hma;vZj1RnUj!p!=+i`;q#18C3IKU+*qn@11)6<3POw8z#B9h1}&>`*2 zJza-k+7I`oQBz0a)T=%99$X8;Fp{mjpb=$HV@zbB7+(dc5sZ(5;?^PJ#loMhOK8=T zy^)`$y>3Q1dSBtOLNEF%62zX5GdYfaa_vhu-#)b+tgnsv0x`*4G~#i5%4?cWa7HMF zLVFYj>^oLrHr<8o#~wpYGGrztaOkyXytR7o1#t?ZW=feLHp;T(4bL`_gQns_BYwfN zS9X+8sD8v8hA1EH#$!!j*?qZOEt~;{ZP%*kf`}nfW2SOIYwqzwEu5ZKq@Esdire~i zP-F>n?(0GcDMNs+kQi^%W#xE;31v?Gfrwy1{Bc(;sD=mju6S-z3<3KZ_nT*upt|sV z=g9eNIK~3j$tnY(&?Vr!`yxVOG_C-fHcxEomJ!h(76fWGyFN_nquYIk~KI;#p|@insI+Epn!8Y^LK3&@L+BZ0Y9mJBDlQ7^4lyNY_}5>c1+ZCAksi# z;UKrw22ajx9Z}|>lVC5ZNN92tcOY1%Ge&=W&3*5G*>_<}Xml9(d*E66Y4SR5qc3#v z7(NXTL%^OK2f;V)9*-pmsZ$GWKGxy8Oy=wR1hdQiUF|8}&B)DUAo?oyC&T~!zek%I z(z(cnqJfpRSPmWYyQ<57B1y9ROGK2SZ6tmeEE|=*!Qyr0VQDE#j-;(1xR@oR? zn$E8rBiuNzapk_w5PZY%fH)2opQmqcHQVlgYCn9t>y#<&meD4o(y_14_rrFy87o)m zG`f^mMtYgFh~eJ@v5y@(@d)$5;Q9~(KK*M43Wv?j@DD#4DxF;4Hw!KWA3^@_5Qfq0 zb|>Tg;0Vv6fpr%vlanVr$NmtE@dlF#W7f+h9p^i-`<3@E>fWA%mHUB8jN<${!4}(X z>UWF7)jgbM{pcJJT2SlZj#He>0C_n zbKcV9EF=vzR|~HQ5S@yWZzy8a{khj>a1u8X=s`2j_XG1(>+RCg^LRzm9X;6%rAU3~ zxTsN<{WZV1JYQ<@XYPc5+xSjmz>=pw5HRpdPTkf0{sMiuSP7_;LE!aH{;+0ei|nsH zaGqK>DZ{JYJojFq%j4=Cvki6)5af<|*zx@YgL=T^-`bwgBEt)pgfXiMdA?5-&GtWJ zY3lu+;l*aEmbE#D)7Sm%48koCXU-0%Sdmm$y5pxm*<5iRO&7*|-C<+JD< zc8Mbgy9N6~jkq8uL3Bv7^PQDEpg9Z#k}5_l>cIF&=d0Ub5dL`9%b zjH%nbRn8f1X4YjF20+`XVs zD)oZ!^z4GZe!Z>q&V5*@q{N20k>prFzG$T8kf`f=q-6Jgc<)UK;jniVsT<2@1{bNe z`Eo;YK(AVs2p-gQ`i6;-Psg5MbC)X$19}&AJYEvQQjWc|DcsV*n0?yV-zFRnVmO3F z*2^WWh>J7%Z8~^=Tt9E)kK-PMI2^Xw-k(|eY2>(L`!4gUZRc5mjFzx>nO57FV))8H zgW4rM&jhXGxqMzK8p;|9loSk{SOcNVZ1ytv)ZL`Nz6fFwmO4U2-2kCch|oj@@Qi z7X3FVpwDG4Uw8>j!=PB-dpvqtUZ`D1h5Nw$iG4-N4`l%}DK%cjdCqgSYif0DXY^4tb#aup#71EbhtyS!2!$Vr!vD zFGCI`3{3>SJad^`h8Hj)fdI~9CB6V8Pl8HSOD;mC#9&KGfYhxpe+@L2BmpeS=Y)ws zKibW5NgLi|Ej0h-8t=Of%S?L6`U9qB5~E0@fj4E!0T%1rq-8CN=iBR%jNw>}Hzx+Z zQ43O2bjpaFr^$*DzbnpQ1uX_nV;;B7C zTx!Nq2svZiH)AXa{-)+o;(46KVgST|mDhf{Kfy*axg-8$%pR~g2)Ma77%#z=y#)FE57ER zTzZMNf=k+oe-Q$PHKX;2jY z|DNVhPDtnTCFW4?3I@lnE=+(F2~Hs7*8mqk0t{c;|%;R32Y5#z>S)`#? zQE{6n#}F#zDFzF@eO+JvfhiZHFmmgHRo)ixqlC;K_6{IG{x6r7%MftzS@H~efFP16 zfWg(1RFXm#jF4gAxSu>qR)n*)h%i_pqj30PnGzbEignA8Xsl~6XN(3GTY~o40f1N5 zOBhYZkt_5SREt6`<3DRk+Xn~UHC0xJ6eXP6FNG4*Lbxr;E9XN2k|~qEyJAKR4qt^6 zs)!Ok+$`KN%KJ>P$`F-GJ744w-RPaKZ+D-`CLN;Y9e0B}{Vy`0`!{MHEp?W?E*z?Gqvx!E$_UeudYK|d*!>2hW7^KC}|h9;iSU<{m7Yp1}^du@F0vP18>3<68TgC(bqF(;k$3=#uck<_pBR4wkxR}p^v5Zj#|OQ^+CaR#oJ zajTh&i}NBtr%2BDl57yq=9`gP5BP2Ff0f811>~g8cc?Xqf_MzO_Z%=SO45K?L*on;?+@V$y0H{;C^>WW&;bm6pT@k<)8X_&U z_%lt#XUFGaLuFXr7g&JmgxXC`R(-!9hloe85V7uG$v9gUg*XqE+oOnrur~c2ahv;U zC?LyPS|n#9xDHhOlOI$Eh@)v`F^f)1z4tC2Q!!Mh8JD2LEwzE53lkb7+9Y?u?3F8K z;<9kvCa^aszXP4(fm{N@HfZ;{#4Cu`4V)DIIeA-3}zomSY-eTi?a zK433r&@OwtEh$SWx+UV*@b>X=oe#%v3oywnjF3xIWIImN&@-xz-K8f#|9i0ow8zjh ze&({>@85sI^f|E?UzNWH~wLkk?$ z0u9p$bvm1GL7R8Q5^B);~=6PG=MD-huoG_X{xqU z5fLf$5R1*?4nnfVZl65-Q`;<{V$)=wyPT)&y%+*y*Mj(&_&YDMUx;_GQm148jovr- zG}>oVz1CFYt8=y<8;|xATOVr^3ie@2E!*o%8xZu%H;WN&j!xf8`O?@*1nw4NNrY7r zB~uDq7-M;zdCW)T6xNn%R(`$2qxLi_d;3>wHjDq3{|t9VH~l?ExO+-c2uyScN1I%Z%rJA!4!KrR`W2H>gNqo!c13;2lz`#S~vmriRB4a3Grmg7{gpZFt`pf=tpwx@i^2iPz&wUK>$+c=M4y#k_t6&O z=zmo+jyE$sbK4f=&(lA9r|b~ayGxJO0Q&ub#rzzmAGj-Kg!@xTSvpeE+);qO;oaD# zOnp1m!r*C@MF;lnGD8c(4nyt(RXzQuftN!qs?&cg@sv;>=wzK%P0)zB9dze!sh3K`hYrS$d+UdmgS|9$1vYdPGio<=)*d6akWVZ(_DWx|Z~R=Nk%| zzLnT3bs8Gq&s=1=)>(iyr-oAQHQEo~U#;D6-eJc}bkN>Z$P!av_X1b?_!cGRieoo> zzK*3KVe>J+%2xR-MoK?%zio7p0Zxq$5g+k2pI{YfV?J4*7!m*pwJjJU{?Xu_Z&R2!<5;H%HA2BK&+DjC5NzII zWZVr~ktt-u_e<3$>vyTRT6ai`@3zT8*F@pN_Q|?^$(AN#NFxLz0$T<|Qzk=yJGRU0 zGU+~v9acC4Q%F6}*)uc=*`G_@6L}FN<e*L%8^2Ns z;R0Wp27q95<59X&43ltvp~mmW!mYiJIx%1Vh?eco!_R1D^l3?^LllCCOZ&iIU)JrD zu_Pj@hJuQb^?5vCwLV`=RPYjW;%eg~)EU$dx|v%<<=W1Y&=o)+amWt#%7$8d)j~O5 zjX!5@QqSsil^sXMX1jn#=DjProHL@^?5P+B+s6^mBdw zbjxMA9JUkn18=$}`yLTa;cAdTu8g|5js1VkJVmQB2&A0xQS_kRmkMS=X9^fV%66Vs<%<5 z<0l%oxjE#wzRX`bqT*6ZzXw)hn+SsNuAeMFvE$Mu)3UNCI$f`X>q6j!X3cN5yS)r= z+ZcaaL(!i)L4Y0?>dk{7#-UpHumr+td^F-b*0ldBW^lnSCa(cRdpHPnjoNbV*6@^_ z2T{TKDeis5oga=QPg{UMOjjJMEQwX#V(&xV?5L#h{IXkdQ|^T`BUr{hxg&uhdJHAN zn2@ou2QO|ys|^f?8=a)-+u)n&riv+}Pwq;JT{z%wmsIA5T&JkD z8Jgm^Q5xy+ci436{X)rr36SomdKlv9(s)ql2W>Dmc}+)*eE=hDGGA}fHNFyu(zBO= zc|;ZTSDIU;f4}zDV8-4ketb+K^Rf=vZV_GgP;FlJg0wX6IPOTG5Es_ghtR+|8)Q-Ig!GLz9(9pO!+LoTX4 zXy8|a=VASJwNuj7qyvL76M{4 z>SaIlyS6x#FtW)iXV!OG$b7(CfeepEFNroeXMhz(ml9{5n|SIG(Xi|)eG1jY*g|AR zVT?@%hs|KNMuaJpDSPkCTAAbaZKdCQmo{Dsk(XV_z3GCkT~Gdjto`G$&f}+yW(?<= zCCK_Kq~o14bamV)@BC+Ee<%%AhY-vvde?Z3PdA%>3T%U~3Wqp(nsy#*1&7x*JGZwL zmy(7mDW;7pC}pM_Sj%72+7Nljx5Ej5Kt(g0rp{?&R!;rCD0mX*HFtQubFSWHdm5j2 zS3e(`&%0xA=-+w0>4GnACAuQFtbvv)5uKLKI_!T#ui+)0-Lk3s<{a1h;ry(gIu`1d z!ApG3zcrBu^@%Iuu^`73OsU%Ir-2~wROQ^aEltx5TcC=pF!+iqigzqnF604hx4s7< zfRYOWh4bK!g?5OY*y0n9M0St?jjiY10oN%?aH+awb@wuVPr5ps^`{A#4$#qMw)RKN);&O$N@nY3kVpbYOdmCSZ z7B^(&^Vs$?yYz4k_E=42f;SYuIm!s+nW&jn7%E9GuK5D)*8z&s_bIgnWb8PWaxt8` zyp!6gxm{8Ym>Kr(;}AcoPP_%?ptqA1ey%JDJiCQjs?+`;r1p}c-_8Vp7*`n zOAh6+NJ7v|))BH0SEL6=u9O;_dJp}NWpq+y|bRmjX)rN?kyJIJ~=R`4US}Lb~ zjnSO3#WwF9q|eT1QHGys(3_muMjzVncR%=L2eMBm%o-_07ml#)isW3%{HjW6jKsN) zD&0wxFJUo=L0b914PjU0eF*jEnfG%rz~KNe_Mh$GN1Q`#YzV&&u4ZFXU8UEpHO+06 z(UL$YR(c*(yXBefI=U>Np<_R|DiLVycTSSoB#Vz5qyeH<+ZoA)2~Px=7sc5NRX|3e zj_jP?`W(HYAm&+FGZP+NK&7lYD@s5WSx)Tq=tcE&7mOP*C|$+@`IjnaKZ2{^*a5d& zA=>AV27+lhz01#k=Kh`EbZ~ACW6F#yZq&f#KGyIFbK4FzGgM#La@DNVRix(b^vgo= z3TJ=G4{C5x_i!SW7+2qNQ;i{pXD6v8O`va%Y?F1!BUhjpCyLdBF+RJtOg4sTop$p3 zDjw->NDd+fSR{RJ&RbOLxrYIdp_}17huf%!O!6kIMVcKbqzD+UA|*o;3n zY{f=mtlCB0%ewin!VL78Ec^Lg4o~Yf4oSM0C)+ zQS9=`ZlQ+lkH~03X$9P**3fRNs5gN4`~G6Rq`W{+>u+oy=B+7LzJ))EhmvwBu0>;C zm^I^EoZGQ1WUc9B*n!Ed;?d~yHun(@^6UX;i^a-c`0Hni^=$*fqOv3LR*0>HCW)TT z`jIH7L60AQ7(V1%9B3(lQ=*}tY0n*?=ys2q+Zt))?)2Xnx66}$`~FGr+|D)g0q7Ig zvW$*t%ibT#wOHY#4iXo~@6TsxW#y!7&+!V!0ccKb`0Wj*G)AnK^QY6Ixbf@CEoPf_ zR<5hX1;NOGg7}cYWOVO>&XZh$2^_0sN!cl)8X4n{m%9e*GiJ>$_xeOR-m3BEZ7=y; zuWK$F`k#R5l%LchEBxzwqV}$z+4}wY-`aEH%O5*RnzB5fDt!B|Y1X*IhauU)xxLQY7MXT<1T%5bOT_)3G-||8jtZlQey2}pY$cs zB7O)H(idm}mnWDnSO7wt%Tls&g~M1mD}jlL5;1%*AaEow!EezMerAY9MqlS~w0|ZF z_kVKkV}vm&;Lbqf2{C;YVZm<%`$&h1JcJD5LEY1pn53~|7BGPn+xoNNttpz+VJN`@ z{0mTAbr*p{2Oxz{OVd3VLrjkk1Khkj6b&JgtymwtAsO@|^wX64j4X3*;lw=qn4-@b z?3*fntyA`pK#BHr?Pn+~e(g=k|H#JmLQFV6ovFK+^-6%#1S=TBpUj~K6Z`(kg|Nes z0k>DF!41Su15?qJSBLEb0B(opl~(34^`zuOaL2{;#%0W+ zWqETHlv`Yz(k#5K2*r`ne~^^MUkv+OS^2XEq%j%(p=U(te+Y}K!~)^G48y}Rnm1PI z_saQtyfdV*3B1e@fN2(6(Jl^0F?19D&@%j_ltobJb^XcV*>sV5U|T7wd>xuqV#||j zegC0Dxtg9SnOvmOIezQksPoPUd}n{$;JHa!I6x=v63jagEv&CY7*6xuFTb^_cpcm8 z=45+}Wc*Gd%V%)Gsm7n2AN%v1`@zp@sj$XqJgt zthxuhL&?k7Wvu08_)B?KQwTl;ArePElnOtz0@ARrzZs~Y{7Bf{#T{Y~*?9Dms9{p~={G}6hCTia=88EwH*ImsBT-Ad)azWyv zC`ap4y|G&u5zHxvq5eeHou}0W=3eZ*s9M!1X(p^Ymg(l$KV_vYAr>O`gNpLi=6`OV z8FTq|%#q3Rjb5HfmDMh;D_8KnKgfeJEawt4YlV3GbtEsNRJ5d={Ahu}x;maxYO}M& zp0osEjU$iV<2)3!U2E*NV)x!{O-duXVOiMq=dP{74H3B`Vg<;}9E#*1i0L-{%}E#u zqy4q@wx5+qB@!^-X8^NQoj(Fz`T*AKMC=G#OCUbjzF?Ir$9b?A1>G0gS%_IZfGnxo z)+H@w;8UOa{oHxqZbFMxk2;O_oQz4`F1+*mIjfP(I@-|o1qR$K!Ctrfog zOEg3nwXaSV^UDJEOp2*HOflZxc4-+ z$d357wddVRk1CfZy)+2&xL47(CHjbzhP)6=d1-hL3@p!J=>;H8Wm=BI+;Vuzk#BAE zZjdi6Buvcwor(XMSDj##kU@hhU_>$!Dj873lh^acH^sl^Oap#T!kG?Hc9lA-)Cdqz`Cd8vL(0<$}*>*!H0^n!e-ONIyY!eDX^v6#C%I4*Pz?v zU3j$Dd}gk5W~gD$cy8E18U(lqk(wSC`A@C;zX6~~9OyBuu%coDKGB`o`_CUzec!i3 z5O3#qvN6BBOx|dkNBE=ES_EgZ6WJ%v3XR{{dFip_p%u8{mAL%_PO8`z%-Z!+z($6l zP23+%EQwDt_`bppkAc6!{?+SqNyl>h2dEi=ml3aD(Qa$w;<)uCF?SGU(rT5`ssae1 zP+0C_?JLfjA=v9qeaQ`OS{{HLgxqZ~hGsE#OTqhU6-c(km(PG>6+&qUTs|M`Gr+D* zZF6{Mx9Ycw%;+6wwv4GDfuBz$HIjhS*84_tNTe6D4=nZd`CAyNwi8wNk3@haWM@2;39Fqw9e~~ z95q!%hlEYX33W0G3W;XxBj=P+g z2G=X@47Lj}&3^;bQ+DLGoQ3_mE~9ZaBTYEQ>*^S#GdA(<78f*c{ACnARjGPBq^A1k zBX_F^9+mkuAIURY#b){}|By*gjvSs@SFL{*r%!wk%LewutPlZw@;V5jHt$>Nx;ojq zBx}pW!Z}X>1?+#;!_^kJ%A547{A?hcn+X-SZbZyz8ISNMeMyqYGRbe3r5aUymP#FX z7fCLY%uISaSjQj4hFdoxTk`n9I;^q5fB5t{A>XRwg4LBck*Bj@A`3_0as(R!BE>@S zS_mGd-Za%>u}3UEgAu_CI3Y)Hnf9a5xaRHXooDYr{7`w8u|L&z@^{NQT~XZ}0G_wm z|Ba$827w#nV=)~TLOn&y75b7)odG23@I{4MHEsJa9@wFd7nT?Q@~_ywy<^~NaciQC zbZwm)f|jL4jDi;ND^-(CN=)t)vN+Ya%yiY49!`~PZEJMA=Y}0ubloauO;&1j8xSEp zYd=m^nH>))$EK&%t}?G#PO(Ju$Ssotx+I$q3JV1`upchYyf^Y5OEPyPSiZh0Bz)Uh zbr70aNJdS*cv`Ods?GI>)8j!2x9b8+I7Z-Fq%Sz1>TW{kH`4omRN2^*kMOiMnyVT2 zmPdC1)?8Be5UJ@xsb1%18A58iq=^Uk9D(L^B5MTI0+?$&@`I@$m3G zkDAy#|KbpC{t>ak2o1Tcf2<3r>Tw&}nD%R8{-Y-E|V$(q{%+BB7glcdCU5P|Z8^g`gI~ zj<$=p5IVXEX7h|oj)szgW(9V5|=vnp&Ri6usY&TnvVWnw{b2g10?hLS2Q zB!;JT#n9cWwS%B+YFIU-qLvK){&>;8_hV&;yFEc8UA#}belLRrM^z>h#dY~WoUbXF zD|}`@gzbXEe1QNojKd+hgd8Y@zR-e`U{84(Xb-PG(FFXCs=gn?8^7g8=fPRbj2`df9~_sZuGWj-03OJAnB-2*FGqE+%`a?PPq zG9dIvT~huKh{bleyHn=sdLAX0JFW7r*PABs8RQnhHFKGPC_Am$?@Y}IzMtv(nI^-& zn~_Cebf(TLy{3}0bNp4T&(wO)f@wJO9v}bjX9dKH`gX=frO_;7-O_ z@H-0l_1B72jsF@}zt=(=$GJArG{9W)>_-0m&(bv{IDnmd3@ZeL0U*|+Cl?>I*9i>QXh4OF9gwM5nt*=v;-Vd%K8%j4njM@h3d++gE}YVVlMB#^XBTRJ7Y@n&R%C&P5c zr!}HTF z>$4&Wm062R4P5PTsilA{yv)5dA#A%oI{UAd6L9n&wC0FX7j>%7r8Z;JQws4Ty!)we z@0**NMt_}mzq)m^AV8)Xuuj{SJ@-n9i|~xT*9|` ze>70xvaC6QGCjPfr*ULoi;xIe|(PX&YRy3QuUkO4^th;g-C~wEb?h5 zxju5&FFQPkEgQDVS&X3s!0m?((nqg9-B3rRmhaa?-b5L{OAfxo{v0XxLW!%4j}Kog zGhYQDT#o08^)_*#4mq`c8VXS7S`Mb~a-@n45J){~g3Q0s8mlr_UVw|OLf6*MBnc=`AIFICijSTl&cIu_l^zj? zfApHj`=&LaDKb_`QTB1WT`da>PJ~H~)hxK4f*n zoI-#+Hg}KLm)+IZjg_|?L6u%pJsDjUqliQmnG8haaVFc9evrN>xeMz}`Ve=FZ`Aqb z*0%f1vid$6xexK>js#;t3<+DWg@*-U;sbq+eyMK>#Kfvt3PXy;L7=s>ZJG{Udo~vM zsd9~-76<>U-1_oQ1pTZVP`2QTBQ-x$1URt%g};3VPKlZq1yTLLPyY&TX3Lar*U~bP#tw6N;V7+JE&mIrloa>AU{~nZ&ZlLtwqAgvb%^&p5O$K~zz&(X2nAI&A zdwKChpMMEyX>S;hGbtZ7OfbMEO6~y`E0m8GIJ~vk^m7B5wi{=-v`g_=WDU!k@0)9V zWv{XP;x<3aI!)-{j`iLntV;4J^||d^Ix6a=R{T`n@wgm~?Um&R-JE%Cjh|m9qkNqf zDgCq3w1^JJe!-Yf7VlM4X2ic~Oks)bex9*FUWQ5|Q+>bJ_WsLoM+^W2u<&j|lq80n25HslmsymC1KdWb9ER!1MVa7I_^&5C-2j}w*@FR3N z^QU%PnC?@@|M|7CJ@3% z(sO=;tTj0#sa&nm{ngil-; z$Vs25ZPOW_7{B|sLUCntrKG4x^dAuJ4uzbM0mkry+PYw8u>~1OVRs-ao?JeMR=G@u zEt7FlQ{Yu;i?y((4(AT9n&y?i2}YGaTxXV%k+}-dW9T<_rO2DcPjtg?8)3Gz~Wt^=-P)X8@#B z5=8LHDt_9ltY;}R1XhjmAz^IB(h5FI%E7}pO5{9(owYlrq65jKrWJOq5=`b1#^22L zLImSa|D&Un&)j7guvD?%VA{vQjVtCe2STPqOXI1wu5&OzyAUN4cxB#i;k0m|?~ z>AZWEX7X%@AD_66J~rHuk8OoUeTm**$HM>M&!#~`y6Ew%fT4bWghA{NCj<-XM41mJ zzV@<0FS|GC1lNgpTj#SzB0i0XwJTjeq|SMneAt1Om?7!fEU(#Q8)-jo*o$)0C=n3 z2C!lw^B5l&ifiF&^6d=2d$7VEm}DZRcX)~HC5)Wl z2ni7Yg)Ok7|YDc>RjxW zMR~ADXa{=Js~xk&*gJAdIQIynge2xjua6u%wgL=kFjC=^60_IT-*c?{Z#_p4iq4|I z8hzM5H1pt+yMUD6de0xlXusjIMo*fC17%_tpkO857F?`&uFt*NTM4qPnjq+lnG<0i%j2QyNDnW$Z&gT@u$grMiP*t{1F1Dwo# zwm#pvy1zto^FNz%dTPA%KO9<2}fO1^*C=s46|^xb%MeT{YhDxm7vc z@SQCGp!FzPr@6c|ysE#q%@8lDx}U?HWtKBz+cxYTHx+m;LA2|};~%Rpmo1E}jLnH=Hm?{Wmx}>5=_1r1| z{sSNpN`_#K9WPHZUIu+;S;xgJfUjuCf?%~P)QGKxrvDEJ_=J5NN*KVky!XrEaD$hZ zO7;}zAYW?DyO7-2l|u@wys9DAUDM2==<3&Roo<&P%a$%m4L_Z%A0eZ1k7!I?(G8M4 zlnNRMw?kvK3t09^Cg!vZh595Cc_YLvHnlC0%dcFQT)wrgHQ z*V#<}?*S1BdWZvO>{mw@!w}+p6bgjG2d-M{wrFc_Zyn$7-J7GIGBgSN0b^8x{~no6 zITYN1%A^W+-ECCgpd(L=%Rgj$8nkCU5&y^ z>;grjr5y?Riq=MW=?xUcoxM|el^|vd{}EcD&HxKp6?eNTc{rw<+@6%fPjZ4-_Hjo+ z!(YQsIi^YXQO!cD_4gl*eV*Gez@f`Waf#GAf`E!zwkHTHyezZB3_;1w-iaU&d60q* zt=C1weABCa1cNTrFd{0;OL!pxPn%4cpD4NhHdUwXOG(oeLHsIZtV1Yq$b=8XoEm|7 zu3FxUp)D@b zoJYr_W{@>o!Tf?>KYauX>-zLUD%4BnUNZ&5^v@6WSZB7hPAe}c*?H$XjFIbQ1rZ~Tch7s( zJ8`i_mc^A#VynRjn_DG6WwRQBn;8Q0<7Q`8g1Fl)W>n`j#&pd+HE@sCLrTl4U0hs? zG+W0NPNbpCsJin9=h5r1W==j`Kf#I(Kf!Qy?H1$F#Mq#6}L z9svN^VhLEaG5AzhA}k&v3GUrmEo!-sr-no!s(x8{A8vEcC} zVb2sQb|}8!rWBd!A{|^^?J-^OtT>2}e)vI35Nh07-3Ja!z)5GH=RcHhwCOD8HAz9( zcB$(1z9MMK(O}jw$CmK>tTMJez)SqzV){P-_dp20$P<&|+U2eChQEBXv>b!{kR5+i zD%Fl5?2v`B=AS<#d#}AodhU5b+8}l#6c{*+NctLL(zAMjtiSvcX*qtQ#9{qnX4{k* zJm3B4OQSTrd5grb=S!RhvcN@P@zV6hqjiatb`pb7@bvad z--A!Xx}{e7uy$$>PN81;-_Mr$P`pSBtW%n3*}@FK?gS?DYZgfC@RhRouvIef)7xbf z`|>ml!Ab^l9|T5=PC8oZmv+G9W=gtuZWmO=Swq`UN9%ZGO-C6-u|i#KeHXy zF;)yMS!6Oh9i>bBaKpFQKAR^aH~r}B)jZ+Z^Wd;IJ&fq$A@Ha0pZ<0znlI#Y<J^v<%>3zOpYHFFr%4O zWs}Y(X?^EeGPvtcGIaloup)sUl8!-{F$Kj5&2^}}S-jA9QU!mmD)FLg2D#0u&OFHgvi4X zehd!_TUBur@2Hh|un4MO*dg1m{Dv&0`UjoVL@nY{VFOI*HeC2NX=rQ5k}l640<2SM z6>qE)ZqI}NbJkII`zsa%lpd_C;xdBYAgqOp=n#QLgH%=ktJuJA$9Xwr4_o8$(?&-+tuHY zhT}KL>i7JG?gJC=>XScx=O(Fty+_)hIjTXtiQW6Kcg-l44An^I)>EbS^v%cw`?K_q z%R%HbaO=G?f}^M>u*XdtN}wg@zeN^ZxK$M>4M!X*^H1L-eYZa>HDgfp)M9@bIxc;I z%#Di|7`YK9y;v3nIzyrsF^f;fg#>v+28a7r4aKAg_I5q*f5~p+Gc}<)1O1h%n@-2 zI0Pa?fKw8_x5nhpywmwIGvJH93=TbfLJmG_Gia|A7(e~TtKLIV z;YHG5hI+In8P0npiF;a(?CX>LKm4iezV}gS#c|Zw%U7DAcxi@pOAAg%8oT#tdF2mJ zE7pxr7BnD96D?jaxjpcDue|p9KIt17kj4XjsCA`5Z+^@4dazNa7gcqN4L(sl>> zhj{mZ4E*P))k8G1ye))UZfMryRu=(%=HX-hPdWTFx zNfgKCxYCMo+u)dtJ+(s`U+u=`zu49od&O`=_(@o+aDSTPVa+mC4+wPZ+23i84#E?~-Ry70~>10oz z?EL2Ur0=n9QZqOztx(V`z+N$}T1DWK#Q7Xa9Nvdz2LuT8H}L4h{0-$V+TWzZHC@P{>NFqL=Lgbz#-re zm`McqWjOhDm`!rD&g2z+KGR7-r3)PgG*&6XYr4XH<~f~C5*Ho>0b9mF;)WT=k>!FRS>1a-#NMq`55I(D|{w8G(B3-^X-heTR!D3jTM!{P?NL-jh zz#$MA0oxYwHKn3T|8W4r_n$Lfta<5w`bF9O^&dzZ6cKB&@oaMpo7Zv~5MpzZ%M~G|QUIK2oqB?!O)PME1P5+0!JATLdjy$KdC^*FVN zr#nHdhy6mp&)VhEuzrnNti)dHl_#&eQ5N$wC(u^q5y;S>r4ICd`{SRA8Xdrl3Jj^Qlt^b9%rrT=I9@- zBaofSL3?kZ07P2Gd}dj*`eLtS=|}zRhT`P_mb^`_T_UHRw^c@<$m5bbJ(!&d8Q)yW z_SY$2$_BN|$RXemh#mn>0@2r@(;day+qydTs&s6Qqj&7P%axa38V;hgnn5?R7?ch8g)3)yM z%KC59m}jGa$(Ek=hwmxl@#?>~G_s|0eii{+*X?tNSFgP&l_J{(=NtkKfyzRF%7JXo zdYhI7F|g_D;EOw?VfO)9I3AOEF&y`dNVJaOmSS4B?4CwiU4OfG4yO1JVX1NCPcN z*r!8LUjqeBEyft#yLQVunmN(J1*Zj#<1z--2oBPgo&yJ^nc51(n>uo>EZ%yGblv?& z8R!|7CM;uWg@TBi^Ky@uc^Dh=Sn3Ik5r_wkPnZ?iAU|Y`N~RdCQ_w$Afx;uK*PL`b z7*myq9PHXBL)af@U`Z=PW zrWQSqzaaIy4oVx2J8I)8N2+i_eB>X`Rai(q+npWLYLT*h@;R^U7=?-|D&$xnyXVDZ ze`lji9MUPLU2qQeG{QbQ92=v+sZfI&CP5Xos%*~N-KT8%g_IE|SmtNm7DLt)6idK4 zhk!$%8W1q;Dc@yuc>DvGw~h4z*(Gadoh~G3)S^@7#QV-W?vQuC>s^MXM>{=wn9n+h z&|W*Q1Yu@rXralY+2qchJLRdTo`l&oM+-V+(d)8h%jCopPt?gRaS3H&3lYb0BqaCi zU^&EfFoc0Z8F0=9AeAiR_&Qa~uVLVgJk{0K88dY3C&FWlU*Ge(9CgHza`GvsWFt{_ zzfj7-$MsBMPZn(6TKrxeNc!b|AGl0_I56%M2An+c$fom&HqC7+eP(ao#Z>=nL<-L< z8LSr)>EIEpyZEcZ9M2{gIa#DU=EqTy zz4v6N`~=7Q8DuayWeScP7=(YcEzvNHBN$}j;RYG-DCAlXMC|OMZL5LPSey((Ij|q5 zB_r6&WyM8XrS;hL(zXnHn9vHvn~v$E*4xj8>%+Le3zrJf4u)ZqR5H+?8W0R$V#u!{ zPOA@N^&&VmiHnnJ5octaDjUQ@7}HE*lFd{0xUueQPdzWo`i77<_Ec$akfne3c3Fs1 zjRvu=OU+AvlF_GLl)W$Rl%|0x>A;eqdMZ~ygC{@1b_I2X!h?WKat^X(a@Lmqy0H$ijCF4#l({-3YHp+c3%!tY4grUNL!gQgU<1kt z97JHJI6OQox88cI9C5@EvSP&wRmNy17rBUDFA#Y?X&FH9I+&+!-@aY`zyJ5&l7aB& zDFnwJdz>m?hz+d^%)lWTE$1D@$C@|kg- zT61UuF69};{wZUlYAw^$iM?J}k1-Y*$Aou+$^|TAqGH3)L>LSaVo<{4dy4j^y6}eg zP*l+RMTH;{FP!fNC*B=UzD&NlUk*O-ge>^86R~axgz9RgZslU>ShZN>R4o4)-6{*P znQqUm_sGFJ9+alNeR>S`IPz0%2aO;=m%>xo!&ggr!~F!7FL6tlI5z&(GpmUY*0t2m zZ%5`#1Lv_v8fvg&heHGpiWMq*c)YPX@4_@S5WWtlI@Ln1I6jco@dKjLr;3U!8S5OA zaPIykWLi9gAaRq#gI+uxY7k3}4$P~S?)h~xzHW(}e$n|dip`&OY{BR#gDGy7>?I{# zTfaT8$S+R$5;?>!1BZY^AWQ_b0k$0&2%B5_YTqY!{`R+W^2sNw;)Q+9=v5H!IrG8hDLd6u2*+G{NoHC!`ol^FbDpq4X0qYi@_Dq8yc`(@vr{c_;oL9lAN zGN!i^XkPhjEpDj%p|k$_;QgV+67ymh%3)kTG4~GSo3!rzdiV;Y!-+Yhe zy3>BC$XCVT7hUaP%?w&SsbrW5NB)?#|1Hw`M2`edv*rKQ;&50=5FbFVFxy`9g*J25t&>zUrzeR%V7xuZ4KgSKZxj5)X@5X z$_Y(R8pw(H($(MwG4vmoh(I4?;_;$%7&j)Z*!Z`$XF~R0bF&O$v)vUJY?Yd2okkhN zW2dRO!QM1+98=x#p7W*ksExAo%hyXYPHw`ADr8AwxT-Vxal7V(7pCVrL5H=k(n7qk z9lNFLotxYleChDHQrbCf zsC`|qk8F6i>=gV$6-b%#b#``2dwaWV+qO+px3{&+8%}tGcK#DsB1DX+M4)nk@u>`; zf`FDnOvfceR60-r!U4l1*Bxumhc(GPJ<^Od+D%POnucLUgApltWVkImUR4h8O`b`UJgI3e!kCWB zR?M<110d5==F2|t0nM8<67&$oDpy7@KpcV6X8!#7$`A9ORK*mng^Y5`P&9=!hPu|% z+asL|I+aI;aXmUm2&6xbHRXKhMI1az8;Q zJ;&MQTMhVWYm#`kbK}p+Ips0$y?O0TOW@!Ye#_p!$(tG!#(t86ov&AvZoud2k5vDi1X+(x7>$H$*hxJQC*Qj*=>|S~Ho=0TSro*M>=))zpdYQ~y zu~1?>(iuyE1g93&Y+f%NCvU=0)W3t?2fPs*@FzHpiT7sQ2U*!!kR#*`)s4h^4j_0T z0Kg`~yY|Yi8-68+Ow>y=ls`zO$`@7H;DKWBsKRUCp)9g*}IA z4qqW>TyPeSm^Se^p1~pCC`b1H#NW$c8*!JPL%<;rJ_2Y-Z@c|=dE*=2 zAd43-Nle-~`7Xtkn!c303rS0*hIKkH@x&(oR9Mv4H^=}se*40eUw}enLO%SV56gf2 z$A8ERFFX&$O0TS3wNl>y{`bof8#hAPfB_K{B2+NkcH3{{XSduU{XP9sgF(rP=xPf(l&YbyWcHq z4qYSn-FKfn{Ktpo;&)yQr9`j%>Q}##EhlV|bI&;!tS8Iq?tAW*d+xqlHo=nT9dCb! z6-y2r=$2c4ev90H{~wGpsHIu99KS{W>Y|I}=9_;a&p-FPOu@|grkie(Cmw%7l`sny zERc`>{YT{oKlr{p``mN#_6sh+60DPyCNX>El~?4NYrZMlx4o=iofa)zC>LDtb~*EH zXR5M>oicIw%oU%J`5p7+eeZjp+;GE>A+4Vh@H#i=nOha8XPJoFreGUEAHWY@QUEGrISS? z%%f*G9`nfYLK_hNGaL89gIQ{}CKCCfRVo!dOz0)mC-p%msuW0<7OWVHG$IWao6BhT zxQzeyDH*-zX&G;6klhPgq~`byvf=W-1}Q`W|DBtTlGlEHpVVU9P{$cOl}%Zk+NvNA z9-%$@`d(={ZUgd*=|`2;nnsxi!wOp7aAMDqn86Wp!{VZ*(R`Fr#Ytl$#wFM}huPSy z>@|@uV^WiqfJ*P@-a6$=gy1^T4grTiGze&Wi58UC30PA61{+uyeBleKRH?-hnnoxTCh!&O#TQ+J*Wht;3fF(0_>GAMRlkS8C1Qd*!SdG?v-a1uce_MoYet!HkPef#&x0}nhP?H%pt z2x)~w3mJSb!u^5n14^q6mOHwU9{eA9^bz^`*S;=$Uf(0+XF=xz={?vhxBupLC~EGJ zdhB37GBlza)phUdmhS!C8gD*N8Gw84`RC>FM<18dPdigdz#&iw0%0k3 zoKmL{)Lq=!kAVGZF_ z_=rP|#4!4OE(ZY_&v7!WDh-nd6(9adus$QQ9yP=O=I(P|T&i9)3`>?qF;0L1idr|! zL;ekzl{b)ourSpNYu=H5dAa*xS#snC>DY=7Fr?8wPbQ%p;c?mO5<@Tk1H@ z0Uh0(mxN6=nzj3r8v!b{(w(tLf-w~^96;UbMFH$@M=5PsE@zx~ro?gT)`VJQLwTq3 z0j-K=nG(_%1z9@P@;z7OvLnJnEOqLoZ-)Ua{keQFx||#W4uN72Fz+n>YU60GS=!mG zq`4;vEi`DE@ut&Hm({BeO|vKx1x@*6q7AMzVcfFi*-Z@aXd_a{8RQ6DzQHP!V zsoc5$-ajZG>o%;D4I4J7l}$ISSg0oA@*65xIJ_ZWtOw&`unM7RG5*mCXV0EJa^)3Q zss+i?ulMzYo~njlb=#W&-LHE9u_u#FC(yg`T0+OuFIc3{)vyvDL7e)%BUSX zcE~?}=AXe24PQ|X`rnj?f>Viq*tZ58p|ITa9ri@h3;kH_(wxel=0`vh|B#N^W{;PV zP_a~Brty9rfg}wdJwuxtE#jN@+)9z8OR4BP ziKVr*O;Dh`_PWH6TdVzJ%hClhfD>lg5MdHac({kj0FGSlZflg~*aWwx8OJCyHTlv1 zW}%HPR>n9$mn3O0(@w(T1Z<5 zhs5>xk&d7bZ<^OA!~K2I&ipYRh{1X_4yzZzM-yeq{88Dh{N{6(S+3FmTD{M_B=V;& ziLYORZ{H{_ z!$xLGqgY_v#=KY-%7k~F(!Q6anXJD|Gn<^s>=Q^a!5`w1_@~dRLqkk6HZVyh)#_y! znzJ6%!+rA`WdM7;oN@l!RG~JGu^vCN$t0G80YZzNUhRV~?{eQd%a?K^?(%a8I0TAD zfZdSkig3&!_jvj3Z{-bJPLM-zs?j8@JIoh_qRD#+X$?{;jp@>RQiPO`9@B`y3%>@N zK?5y<@KuT%*kb<@ZurW5SO^~XeEHnxK8I6~j#0l2>o>?d-tl(%{`Y<$Pe1dtDp{z6 zU~koq&3dVLAs$q!yzvchldq(emv-6VFUC!dsq7CVf~SfWQMe+~->mTIBZ7(9dQRI+MyJt-;_@nhMmkpsCR+`M4|GxF_-;&{>VOhI=tuBM2 z)r2??|%2Ya?Q7{krTFT(Y%RWGpv8U^rbJ!`t|FT-=mH?S}u6|1#-g= zZjh&+eoAHQ@9Wp)U9Ih{s%WC(hi>)i)pGrJu9Llc_i4Fs@0Ypah&RjHIzH2}yjeCZ zFFMu%TCoib3}{)ftm?55G|SpKhk!$1E+Sysis>Y`Z3^3e)4bpY4{9A1jKp+V6c&!z zp4l%gK(cK7C7a6t1Tv#=-kxDC06&t|iDqsjZU+j4^ti z-!9E(pQ?SWtUE+j{M|cb-yOe~fnEEhv1OjL9==*OUwD=@9=R4wfdLXHIsC|@QHGmm zQ+5oex`g|m;2*{a%vzHnksf=kg1{4w;XD~=0F&@IHuXK^L+_H|Gq+1GEL2{3@&$<> z98l#;Kg?DZp0-7nzR4^*hDnO-dv2R9Iik+RSW=KMiSoZuxaelU$h{6@nO2xtbPi6+ zp1U8ArOVI5iAwN4bfIkg%m-yZmM`smV+HU^I&2s7~r^*IQ zhS|u5;dHPT2BADO%8>`NH;N71*ntElvvjpE(Ot1(xqf7!(t-nzHEY)DlAWIJ9-Ry? zz(%tC3U%ye6(S6UMgES(4{4bb*kjSK*;!^oEQP_^wZy@$_FOU zFFgFxBux_~V20eb8d&O|TMWxp=Ea{@sN)=589!)=| z{&;dw0xW=hHe9x%g;+jSAsBqP7+j%{DD zP8BbiBopL}vLk&;YJ-U!oG*Lc>ZpqWmM?h5U{yK~8iTH9x69I(U&jWg%VBDQKJMH% z%d)fIjP)QS1c%A766J|QI8NE`NRmniRTSAEc#=ClGF<;MKj5UB-g5TMKrcRF=B;kD z-vDau4bptlkXdq!l{_kAbF&W@h$E zoH*5C3{;~td6QuaHWyidetz#S?v{ne9VKAz?@?3T-0=q8|M~^)Id=$8LL;2OUpixFv zFOrkqezr`~vI=oyC?k%sI3}%B^r~{%5gR7`$G62-E49J$JST8aR09I$T}ESy zcUg~+)858*IoT!P#G0G3o{mjl4?T2EvMtCS9lrVLF_{_vcpY#~I;90forew{l*OdH z#*fcNHa3rJJN4LO;==!k#Jx#0E}q$GQPFV|j;h}C+8+7WFaE2{Uoc+|h2_e+!`8`J zXPpI0qeBoM12uGRTv}w>V+dh4 zYn9#uz0!^SXI8CRrTMeN=Vrykj2KaIMT;4xx1BtnH*eW24?py<-1h6=$i2V2SFxhC z%t@!5BqyJAa>CNX;E~3B1wEQF3(62NW(xBUkZ9SMk|zzX40!9g|M|iHDI++wipQPP zQE5g;3jEK#eQ4#BC8@@YVkgj_*{|;`H=AcI;>4gNiBnO6?yiPE=7UK5N2LUc(Vd@wNHnABRlf6$rBa1d4BejjNWTa&X=+LSJtCFMz zo59qwYh|8z=3X(iJWbJ4hG@&i&UTad< zAn`U>pQ->zKtZ7&iM_N(_WkG=GH?G7tXXPg2>vv{RJs-?;=m+EYWzhy{98EHAayv> z`uWd&Qx5&xzm(Ruo-8l}GV-=Uwite#^fNEg!e+dJW2R%f_DCB*X@nn_FA)QqA2$l- z7=o*0m=7Hl4)E5oDS0p=*iel!>;6zWD`E1U(sLwX+$1U>PcS+NOQ!v>co{mRLr&yU zBaG*0H67QnpASe=h!uyGefKJkF~^WYz#-reAOg10Wyd!>sX+gGR57^OdzBM)-Pe1hiXw^(6XT5DKGD~*nh4!Ht*z3^n8r=EIR zEjgZg@+o=Zu_xp=zxj=P{_4-G;(`-q9r${Qb%167M`^LwGm~gq%5eQRy%>i|8jdU& z)%-As^LitHQ8L}g=!h1dnvrr)MzdWE!g4v2Yg%Z+6U)`Gw8ifKLYm}L2R9rr@d*sn zdCYLRlj2H$g!7+u6c|I$(V#C!Jn}j9^mZA9 ziXleJSnR3NfW10YQRHc}Y7wy&ij(;MJ{f%SRSYbku<3>J1Zi8qL&I21c6{we(t6i} z*iiRP5?{AM>e^c*1|>c>%#A^tF}V*5e_ni5_TF^6v~&+jCloK-Y?n%((e42mdh#Wi z!T|wwTrvf#nZ!oC6dzPzQ>)6Jj!`U$Juo4A|Ltb!yZu3#f6f^aTfbTwaH^6jUH}l6 z9*twklf3ep^xkp5j6C_(}@k<{GfhQ$+c zwN;6#O7>bSB?u^RVn&rAuyEO-y!TbhIi+It;S( zOAUNHcDXc$82>$vXkLRZ&*J?ZG0YrZ8z14Fvk&Jjm!M2DALE<}Y6l@a8HLkRxU z{}@GBC51ktlO%n*F>H8CXZhC$^)Po#w;Y@6zKo-wc?uG(O&Awh737pYWaG8H9p9fI z{}eU^eD#%AiCeV)lY5NQk)>*;dxMCbyHa@w2mx({ z(O%FhgD%-Bo9C$ihTT*Z%Rl*GTK-y~d|5g;CB0w$AuI*BX9-s1;5$YOln{7iSFAr= z5{}B31*15&_j|vP12_`74pZ3Xp-E|?k^~A0wN6ItI6kD%8lfKjDL1vP1Kr`>gR=Mk zTr2fU7D#Q|JbdJska}2{PC((YhzbW3PWQiDt0&O3;@Ii7ktxvSUT<+}hjmCF@*MJp zCGAiEwSrIG!;j+yEJ1lD{vb`5(w6HWhA!;)WI&IOhpU6>JELv64XX)rMhC2l%IA ziDQ`gLs-)Nf4?i^u+G75bJ7BN+reKgtU#F$$m%uhdPO;^(Ge5CE<+fK^`KmNs?p$z zcKNdl{+oR%~;s#f{Jfv)*7k%Th+IT_;a90Cr3ibOz1 z0A3fvcbZIkkLDI#JUTyaj(Yb!cggy7hslaVR$%gNIx!?s=k<@~A#hXc%g9R^#>Gs~ zAx63|VGz$kgR)y3n5F+quC|F9&ZS04jfHkb#2X3B&%**2#@oMtzbYQM&k2{(%%49W zmM(`$S9iDE@WUIV2PY$Oa5c|c7BW0ItV?mop0P@S0)~W<5fwD_(=R!iUp%qT$^gEG z@${iuC^%^4LaUTs?3bc-0O_fG;nK3-|NeeGZu+8&F7gl{zb22pd-uxHrAuMGgHw&* zJAjj%Y+t|{<^Qrse=2@9ZQi6ygYLWcUODab)1(D^wHO?xkk{{FVKu6!9C5#vah%*l z^I-nSYhsxUcq145!!jprDyk?0EnpTbSfGlSl`B^&HdkGB74YlQ@-Z7EGjXLmttrzo zX|O{y0)n4j=$Z9hqj&V)CwhqvrQrH+Kp84yK4gn&N zx8CJ}Nb6&^3YRC%YzUgE_esl%c;kkue>%SNG`7reQIt!tW-^17Tpoq7$l7M5W`ApB zr`1SXO`S9k)9Mr-T(H*&EkM{8TfCq@P{#`u9JFSkBBH)$ROVx&^B6yza19YZmefKe zfIgbM(N}rIA1WeR5Z*XCDIKufnA&jw$^(2Tp;alaa1tVE zF+KXxhLJJpgi>N5tXg@>PYfSM5CbU?!82i~LFAgz3F)i@x7yz#Ey|bsvd|(mJ~|HT z5hyT_20OdR*zknZ4UU+xBErcB$<;#PQiFVu1!%(qt{wojfzG^fEGMSbE3Q;L5f_bt zKfD^{9`eHj;i$on`8Pv(G=(uxEQb6bM;(DhepIy3aj6uMRA%PzwEidz@;$ZWtb;a@J7;*?W1m-vbR4Q=U0gvas
93r;mU{D>pa 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 Date: Wed, 26 Apr 2023 10:45:10 +0200 Subject: [PATCH 15/73] address feedback --- src/executor_config/README.md | 41 ++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/executor_config/README.md b/src/executor_config/README.md index 8686ec3..9cf80c1 100644 --- a/src/executor_config/README.md +++ b/src/executor_config/README.md @@ -1,22 +1,19 @@ # Executor Config As previously mentioned, the xcm-executor is the first Cross-Consensus Virtual Machine(XCVM) implementation. It handles the correct interpretation and execution of XCMs. -Each chain that implements the xcm-executor, can configure it for their use case. +Each chain that uses the xcm-executor, can configure it for their use case. In this chapter we will go over this configuration, explain each config item and give some examples of pre-defined solutions for these items. ## XCM Executor Configuration -Below we list the [Config](https://paritytech.github.io/polkadot/doc/xcm_executor/trait.Config.html) of the xcm-executor. -The Config is implemented with as a trait that expects multiple Types. -Each Type specifies the traits that the implementation must have implemented. +Below we list the [Config](https://paritytech.github.io/polkadot/doc/xcm_executor/trait.Config.html) trait of the xcm-executor. +The Config trait expects multiple associated types. +Each type has a trait bound which the concrete type must implement. Some of these types will use a default implementation in most situations (e.g. RuntimeCall). Other types have a default implementation specified by the unit type `()`. -There are also types that are highly configurable, and in certain cases will have multiple implementations (e.g. -Barrier). -These implementations are then grouped using a tuple `(impl_1, impl_2, ..., impl_n)`. -The execution of the tuple type is consequtive. -For most of these types there are pre-defined solutions. +Most types you'll want to carefully choose which implementation they get. +For most of these types there are pre-defined solutions and building blocks you can use and adapt to your scenario. These solutions are listed in the xcm-builder [folder](https://github.com/paritytech/polkadot/tree/master/xcm/xcm-builder/src). We will now explain each type and go over some of the implementations of the type: @@ -51,8 +48,32 @@ pub trait Config { } ``` +## How to use multiple implementations. +Some associated types in the Config trait are highly configurable and in certain cases will have multiple implementations (e.g. Barrier). +These implementations are then grouped using a tuple `(impl_1, impl_2, ..., impl_n)`. +The execution of the tuple type is consequtive, meaning that each item is executed one after another. In most cases the execution is halted when one of these items returns positive (Ok or true, etc.). The next example of the Barrier type shows how the grouping works (you don't have to understand what the implementation does). + +```rust,noplayground +pub type Barrier = ( + TakeWeightCredit, + AllowTopLevelPaidExecutionFrom, + AllowKnownQueryResponses, + AllowSubscriptionsFrom, +); + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + ... + type Barrier = Barrier; + ... +} +``` + +## Config Items +We now go over each config item to explain what the associate type does and how it is used in the xcm-executor. Many of these types have pre-defined solutions that can be found in the xcm-builder and a good way to understand these configurations is to look at example configurations. On the bottom of this page we listed some examples. + ### RuntimeCall -The `RuntimeCall` type is equal to the RuntimeCall created in the `construct_runtime!` macro. +The `RuntimeCall` type is equal to the RuntimeCall created in the `construct_runtime!` macro. It is an enum of all the callable functions of each of the implemented pallets. ### XcmSender The XcmSender type implements the `SendXcm` trait, and defines how the xcm_executor can send XCMs (which transport layer it can use for the XCMs). From 016b0cb05408340af7e975817c0122fe5a10e9ac Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Wed, 26 Apr 2023 10:47:25 +0200 Subject: [PATCH 16/73] address feedback --- src/fundamentals/multilocation/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fundamentals/multilocation/README.md b/src/fundamentals/multilocation/README.md index 25b11b5..7f85e9f 100644 --- a/src/fundamentals/multilocation/README.md +++ b/src/fundamentals/multilocation/README.md @@ -5,9 +5,9 @@ MultiLocations are used to identify places to send XCMs, places that can receive ### Location is relative MultiLocation always expresses a location relative to the current location. -You can think of it a bit like a file system path but where there is no way of directly expressing the “root” of the file system tree. +It can be thought of as a file system path, without the ability to directly express the “root” of the file system tree. This is for a simple reason: In the world of Polkadot, blockchains can be merged into, and split from other blockchains. -A blockchain can begin life very much alone, and eventually be elevated to become a parachain within a larger consensus. +A blockchain can begin as a standalone sovereign chain, and could eventually be elevated to become a parachain within a larger consensus. If it did that, then the meaning of “root” would change overnight and this could spell chaos for XCMs and anything else using MultiLocation. To keep things simple, we exclude this possibility altogether. From adad046ae3c2fe08419f1e750411491189afb3ac Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Tue, 2 May 2023 13:01:43 +0200 Subject: [PATCH 17/73] address feedback --- src/executor_config/README.md | 47 ++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/executor_config/README.md b/src/executor_config/README.md index 9cf80c1..13abca8 100644 --- a/src/executor_config/README.md +++ b/src/executor_config/README.md @@ -1,8 +1,8 @@ # Executor Config -As previously mentioned, the xcm-executor is the first Cross-Consensus Virtual Machine(XCVM) implementation. -It handles the correct interpretation and execution of XCMs. +As previously mentioned, the xcm-executor is the a Cross-Consensus Virtual Machine(XCVM) implementation. +It provides an opinionated interpretation and execution of XCMs. Each chain that uses the xcm-executor, can configure it for their use case. -In this chapter we will go over this configuration, explain each config item and give some examples of pre-defined solutions for these items. +In this chapter we will go over this configuration, explain each config item and give some examples of the tools and types that can be used to configure these items. ## XCM Executor Configuration @@ -51,7 +51,7 @@ pub trait Config { ## How to use multiple implementations. Some associated types in the Config trait are highly configurable and in certain cases will have multiple implementations (e.g. Barrier). These implementations are then grouped using a tuple `(impl_1, impl_2, ..., impl_n)`. -The execution of the tuple type is consequtive, meaning that each item is executed one after another. In most cases the execution is halted when one of these items returns positive (Ok or true, etc.). The next example of the Barrier type shows how the grouping works (you don't have to understand what the implementation does). +The execution of the tuple type is sequential, meaning that each item is executed one after another. Each item is checked, if it fails to pass, the next item is checked and so on. The execution is halted when one of these items returns positive (Ok or true, etc.). The next example of the Barrier type shows how the grouping works (understanding each item in the tuple is not necessary). ```rust,noplayground pub type Barrier = ( @@ -69,6 +69,8 @@ impl xcm_executor::Config for XcmConfig { } ``` +In the above example, when checking the barrier, we'll first check the TakeWeightCredit type. If it fails, we'll go on to check the AllowTopLevelPaidExecutionFrom and so on until one of them gives a positive. If they all fail, a `Barrier` error is thrown. + ## Config Items We now go over each config item to explain what the associate type does and how it is used in the xcm-executor. Many of these types have pre-defined solutions that can be found in the xcm-builder and a good way to understand these configurations is to look at example configurations. On the bottom of this page we listed some examples. @@ -76,7 +78,7 @@ We now go over each config item to explain what the associate type does and how The `RuntimeCall` type is equal to the RuntimeCall created in the `construct_runtime!` macro. It is an enum of all the callable functions of each of the implemented pallets. ### XcmSender -The XcmSender type implements the `SendXcm` trait, and defines how the xcm_executor can send XCMs (which transport layer it can use for the XCMs). +The `XcmSender` type implements the `SendXcm` trait, and defines how the xcm_executor can send XCMs (which transport layer it can use for the XCMs). This type normally implements a tuple for one or more [transport layer(s)](Todo Transport Layer Link). For example a parachain can implement the XcmSender as: ```rust,noplayground @@ -87,8 +89,8 @@ For example a parachain can implement the XcmSender as: XcmpQueue, ); ``` -If a parachain does not implement the XcmpQueue, it will not be able to send messages to other parachains. -This can be useful for controlling the destinations that an XCM can be send to. +If a runtime does not contain the XcmpQueue pallet as a config item for XcmSender, it will not be able to send messages to other parachains. +This can be useful for controlling the destinations that an XCM can be sent to. ### AssetTransactor @@ -99,23 +101,38 @@ Three default implementations are provided in the xcm-builder, namely the `Curre ### OriginConverter The `OriginConverter` type implements the `ConvertOrigin` trait and defines how the xcm-executor can convert a `MultiLocation` into a `RuntimeOrigin`. Most xcm-executors take multiple implementations in a tuple for this configuration as there are many different MLs we would like to convert. -Take for example the `SignedAccountId32AsNative` implementation, that can convert an `AccountId32` Junction to an signed RuntimeOrigin. -There are many pre-defined solutions in the xcm-builder (e.g. -converting parachains (siblings or children) and relay chain or root origins). +When multiple `OriginConverter`s conflict, the [OriginKind](https://paritytech.github.io/polkadot/doc/xcm/v2/enum.OriginKind.html) that is passed to the `convert_origin` function is used to distingues which `OriginConverter` to use. There are four different `OriginKind`s : + +```rust,noplayground +pub enum OriginKind { + Native, + SovereignAccount, + Superuser, + Xcm, +} +``` + +An example of the use of `OriginKind`s are the `SovereignSignedViaLocation` and `SignedAccountId32AsNative` OriginConverters (defined in xcm-builder). The first converts an sovereign account into a `Signed` RuntimeOrigin (uses `SovereignAccount` OriginKind) while the second converts a local native account into a `Signed` RuntimeOrigin (uses `Native` OriginKind). ```rust,noplayground -// ThisNetwork = Kusama; -SignedAccountId32AsNative; +pub type SovereignAccountOf = AccountId32Aliases; +( + // A `Signed` origin of the sovereign account that the original location controls. + SovereignSignedViaLocation, + // The AccountId32 location type can be expressed natively as a `Signed` origin. + SignedAccountId32AsNative, +); + ``` ### IsReserve The `IsReserve` type must be set to specify which `` pair we trust to deposit reserve assets on our chain. -We can also use the unit type `()` to prohibit reserve asset transfers. +We can also use the unit type `()` to block `ReserveAssetDeposited` instructions. An example implementation is the `NativeAsset` struct, that accepts an asset iff it is a native asset. ### IsTeleporter The `IsTeleporter` type must be set to specify which `` pair we trust to teleport assets to our chain. -We can also use the unit type `()` to prohibit asset teleportations. +We can also use the unit type `()` to block `ReceiveTeleportedAssets` instruction. An example implementation is the `NativeAsset` struct, that accepts an asset iff it is a native asset. ### UniversalLocation @@ -132,7 +149,7 @@ X2(GlobalConsensus(NetworkId::Polkadot), Parachain(1000)) ``` ### Barrier -Before any XCMs are executed, they need to pass the `Barrier`. +Before any XCMs are executed in the XCM executor, they need to pass the `Barrier`. The `Barrier` type implements the `ShouldExecute` trait and can be seen as the firewall of the xcm-executor. Each time the xcm-executor receives an XCM, it check with the barrier if the XCM should be executed. We can also define multiple barriers for our `Barrier` type by using a tuple. From c737d9b3c07062c586d417b96a2d0a6c04f11b87 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Fri, 5 May 2023 14:02:36 +0200 Subject: [PATCH 18/73] address feedback --- src/fundamentals/multiasset.md | 8 ++++---- src/fundamentals/multilocation/README.md | 6 +++--- src/fundamentals/multilocation/junction.md | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/fundamentals/multiasset.md b/src/fundamentals/multiasset.md index df1b15b..5eb1008 100644 --- a/src/fundamentals/multiasset.md +++ b/src/fundamentals/multiasset.md @@ -8,7 +8,7 @@ Some manage assets that are not fungible, such as Ethereum’s Crypto-kitties It was an early example of such non-fungible tokens or NFTs. XCM is designed to be able to describe all such assets without breaking a sweat. -For this purpose, there is the `MultiAsset` datatype, along with its associated types `MultiAssets`, `WildMultiAsset`, and `MultiAssetFilter` datatypes. +For this purpose, there is the `MultiAsset` datatype, along with its related types `MultiAssets`, `WildMultiAsset`, and `MultiAssetFilter`. ## MultiAsset Breakdown Let's take a look at the MultiAsset struct: @@ -34,10 +34,10 @@ enum AssetId { The asset identity is expressed in one of two ways; either Concrete or Abstract. Abstract identities allow assets to be specified by a 32-byte blob. -This is convenient, but it relies on the receiver to interpret the blob in the way that the sender expects, which will require consensus between the sender and the receiver, and may not be simple to achieve. -Concrete identities uses a `MultiLocation` to identify an asset unambiguously. +This is convenient, but it relies on the receiver to interpret the blob in the way that the sender expects, which will require a common definition between the sender and the receiver, and may not be simple to achieve. +Concrete identities use a `MultiLocation` to identify an asset unambiguously. For native assets (such as DOT), the asset is identified as the chain which mints the asset (the Polkadot Relay Chain in this case, which would be the location `..` from one of its parachains). -Other assets (e.g. non-native assets or NFTs) can be identified by a `GeneralIndex` junction. Depending on the implementation of the encaptulating consensus system, the exact location may differ (e.g. `GeneralIndex(AssetID)` or `PalletInstance(PalletID)/GeneralIndex(AssetID)` can both be valid asset identities). +Other assets (e.g. non-native assets or NFTs) can be identified by a `GeneralIndex` junction. Depending on the implementation of the encapsulating consensus system, the exact location may differ (e.g. `GeneralIndex(AssetID)` or `PalletInstance(PalletID)/GeneralIndex(AssetID)` can both be valid asset identities). ```rust,noplayground enum Fungibility { diff --git a/src/fundamentals/multilocation/README.md b/src/fundamentals/multilocation/README.md index 9d7f7f1..48439a0 100644 --- a/src/fundamentals/multilocation/README.md +++ b/src/fundamentals/multilocation/README.md @@ -1,7 +1,7 @@ # MultiLocation The [MultiLocation](https://paritytech.github.io/polkadot/doc/xcm/v3/struct.MultiLocation.html) type identifies any single location that exists within the world of consensus. -It can represent all manner of things that exist within consensus, from a scalable multi-shard blockchain such as Polkadot down to a ERC-20 asset account on a parachain. -MultiLocations are used to identify places to send XCMs, places that can receive assets, and then can even help describe the type of an asset itself, as we will see in [MultiAsset](../multiasset.md). +It can represent all manner of things that exist within consensus, from a scalable multi-shard blockchain such as Polkadot down to an ERC-20 asset account on a parachain. +MultiLocations are used to identify places to send XCMs, places that can receive assets, and can even help describe the type of an asset itself, as we will see in [MultiAsset](../multiasset.md). ### Location is relative MultiLocation always expresses a location relative to the current location. @@ -13,7 +13,7 @@ To keep things simple, we exclude this possibility altogether. ### Hierarchical structure Locations in XCM are hierarchical; some places in consensus are wholly encapsulated within other places in consensus. -A parachain of Polkadot exists wholly within the overall Polkadot consensus and we call it an interior location. +A parachain of Polkadot exists wholly within the overall Polkadot consensus; we call this an interior location. Or a pallet exists wholly within a parachain or relay chain. Putting it more strictly, say we have two consensus systems, A and B. If any change in A implies a change in B, then we say A is interior to B. diff --git a/src/fundamentals/multilocation/junction.md b/src/fundamentals/multilocation/junction.md index ecff45d..e1550c1 100644 --- a/src/fundamentals/multilocation/junction.md +++ b/src/fundamentals/multilocation/junction.md @@ -91,7 +91,7 @@ Non-descript indices and keys within the current context location. The usage will vary widely owing to its generality. An example use case for the `GeneralIndex` is to describe an Asset within an Assets Parachain. -NOTE: Try to avoid using this and instead use a more specific item. +NOTE: If possible, try to avoid using this and instead use a more specific junction. #### AccountIndex64 The `AccountIndex64` can be used to describe an account index. From 808e2ad965a252d1a9aa101885d7895799695d50 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Fri, 5 May 2023 16:36:53 +0200 Subject: [PATCH 19/73] address feedback --- src/executor_config/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/executor_config/README.md b/src/executor_config/README.md index 13abca8..5d48679 100644 --- a/src/executor_config/README.md +++ b/src/executor_config/README.md @@ -1,5 +1,5 @@ # Executor Config -As previously mentioned, the xcm-executor is the a Cross-Consensus Virtual Machine(XCVM) implementation. +As previously mentioned, the xcm-executor is a Cross-Consensus Virtual Machine(XCVM) implementation. It provides an opinionated interpretation and execution of XCMs. Each chain that uses the xcm-executor, can configure it for their use case. In this chapter we will go over this configuration, explain each config item and give some examples of the tools and types that can be used to configure these items. @@ -51,7 +51,7 @@ pub trait Config { ## How to use multiple implementations. Some associated types in the Config trait are highly configurable and in certain cases will have multiple implementations (e.g. Barrier). These implementations are then grouped using a tuple `(impl_1, impl_2, ..., impl_n)`. -The execution of the tuple type is sequential, meaning that each item is executed one after another. Each item is checked, if it fails to pass, the next item is checked and so on. The execution is halted when one of these items returns positive (Ok or true, etc.). The next example of the Barrier type shows how the grouping works (understanding each item in the tuple is not necessary). +The execution of the tuple type is sequential, meaning that each item is executed one after another. Each item is checked to see whether it fails to pass, then the next item is checked, and so on. The execution is halted when one of these items returns positive (Ok or true, etc.). The next example of the Barrier type shows how the grouping works (understanding each item in the tuple is not necessary). ```rust,noplayground pub type Barrier = ( From 0f4ae96870044b485b346ad791452bf383d6f409 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 20 Apr 2023 17:28:01 -0300 Subject: [PATCH 20/73] Add transfers section --- src/SUMMARY.md | 6 ++- src/journey/README.md | 4 ++ src/journey/transfers/README.md | 67 ++++++++++++++++++++++++++++++ src/journey/transfers/reserve.md | 1 + src/journey/transfers/teleports.md | 26 ++++++++++++ 5 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 src/journey/README.md create mode 100644 src/journey/transfers/README.md create mode 100644 src/journey/transfers/reserve.md create mode 100644 src/journey/transfers/teleports.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index c826b5c..28b5303 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -15,8 +15,10 @@ - [MultiAsset]() - [Responses]() - [Fees]() -- [A Journey through XCM]() - - [Transfers]() +- [A Journey through XCM](journey/README.md) + - [Transfers](journey/transfers/README.md) + - [Teleport](journey/transfers/teleports.md) + - [Reserve-backed](journey/transfers/reserve.md) - [Fees]() - [Expectations]() - [Queries]() diff --git a/src/journey/README.md b/src/journey/README.md new file mode 100644 index 0000000..79c331e --- /dev/null +++ b/src/journey/README.md @@ -0,0 +1,4 @@ +# A Journey through XCM + +This section will be a step-by-step, practical introduction to all the features XCM has. +We'll learn instructions one-by-one and build different messages with them along the way. diff --git a/src/journey/transfers/README.md b/src/journey/transfers/README.md new file mode 100644 index 0000000..6556d53 --- /dev/null +++ b/src/journey/transfers/README.md @@ -0,0 +1,67 @@ +# Transfers + +The first feature you'll be interested in when dealing with XCM is being able to transfer assets between consensus systems. +In the [Quickstart](../../overview/README.md) section, we saw a simple XCM that when executed, would send assets between two accounts on the same consensus system. +This can, of course, be done locally as well. The beauty with XCM is we can send it to another system for them to execute. +The end result is what's called a "remote transfer", making a transfer between two accounts on another system. + +Now that we've learnt the [fundamentals](../../fundamentals/README.md), let's go over those same instructions. + +## WithdrawAsset + +```rust,noplayground +enum Instruction { + ... + WithdrawAsset(MultiAssets), + ... +} +``` + +This instruction is the most common way to get assets to the holding register of the XCVM. +The `MultiAssets` in the operand will be stored in the holding register to be later used for other instructions. +As we've seen, we can use the expression `(Here, amount).into()` to take a certain `amount` of the native token. + +## BuyExecution + +```rust,noplayground +enum Instruction { + ... + BuyExecution { fees: MultiAssets, weight_limit: WeightLimit }, + ... +} +``` + +Because XCM is designed to be agnostic to the underlying consensus system, it doesn't have fee payment baked in. +This instruction lets you pay for the execution of the XCM using the assets in the holding register. +Most XCMs are not allowed to be executed (blocked by the [barrier](TODO:link)) if they don't contain this instruction as one of the first ones to pay for all future ones. + +## DepositAsset + +```rust,noplayground +enum Instruction { + ... + DepositAsset { assets: MultiAssetFilter, beneficiary: MultiLocation }, + ... +} +``` + +This instruction will put assets from the holding register that match the [MultiAssetFilter](../../fundamentals/multiasset.md) into the `beneficiary`. + +## Examples + +```rust,noplayground +let message = Xcm(vec![ + WithdrawAsset(), + BuyExecution { }, + DepositAsset { }, +]); +``` + +## Transferring between systems + +But what if you want to make a transfer from one system to another? +There are two ways of transfering assets between systems: +- Teleporting +- Reserve-backed transfers + +We'll be discussing them in the following chapters. diff --git a/src/journey/transfers/reserve.md b/src/journey/transfers/reserve.md new file mode 100644 index 0000000..960cfcc --- /dev/null +++ b/src/journey/transfers/reserve.md @@ -0,0 +1 @@ +# Reserve-backed diff --git a/src/journey/transfers/teleports.md b/src/journey/transfers/teleports.md new file mode 100644 index 0000000..0470dce --- /dev/null +++ b/src/journey/transfers/teleports.md @@ -0,0 +1,26 @@ +# Teleporting + +Asset teleportation is one of the ways we'll see for sending assets from one chain to another. +It's the simpler method of the two as it has only two actors, the source and the destination. + +The process follows these steps: +- The source gathers the assets to be teleported from the sending account and *takes them out of the circulating supply*, taking note of the total amount of assets that were taken out. +- The destination grabs the teleported assets and *puts them into their circulating supply*, depositing them to the beneficiary account on the destination. + +We'll go over the basic XCM instructions that achieve this, then some additional ones, and then see some examples. + +## InitiateTeleport + +This instruction takes the teleporting + +In Rust, we'd call it like so: + +```rust,noplayground +InitiateTeleport +``` + +```rust,noplayground +InitiateTeleport() +ReceiveTeleportedAsset() +DepositAsset() +``` From 1b7c9e38ed0935d608365336333aa0efe89ac741 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Fri, 28 Apr 2023 18:02:18 -0300 Subject: [PATCH 21/73] Add transfer chapter to journey --- src/SUMMARY.md | 4 +- src/journey/README.md | 3 +- src/journey/transfers/README.md | 43 +++-- .../transfers/images/asset_teleportation.png | Bin 0 -> 9809 bytes .../images/reserve_asset_transfer.png | Bin 0 -> 17770 bytes .../transfers/images/source_is_reserve.png | Bin 0 -> 27979 bytes src/journey/transfers/reserve.md | 158 +++++++++++++++++- src/journey/transfers/teleports.md | 128 ++++++++++++-- 8 files changed, 294 insertions(+), 42 deletions(-) create mode 100644 src/journey/transfers/images/asset_teleportation.png create mode 100644 src/journey/transfers/images/reserve_asset_transfer.png create mode 100644 src/journey/transfers/images/source_is_reserve.png diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 28b5303..558ce69 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -17,8 +17,8 @@ - [Fees]() - [A Journey through XCM](journey/README.md) - [Transfers](journey/transfers/README.md) - - [Teleport](journey/transfers/teleports.md) - - [Reserve-backed](journey/transfers/reserve.md) + - [Asset teleportation](journey/transfers/teleports.md) + - [Reserve-backed transfers](journey/transfers/reserve.md) - [Fees]() - [Expectations]() - [Queries]() diff --git a/src/journey/README.md b/src/journey/README.md index 79c331e..fcaa9ea 100644 --- a/src/journey/README.md +++ b/src/journey/README.md @@ -1,4 +1,5 @@ # A Journey through XCM This section will be a step-by-step, practical introduction to all the features XCM has. -We'll learn instructions one-by-one and build different messages with them along the way. +We'll create XCMs for a variety of use cases, learning about all the instructions available to us along the way. +Let's step right in. diff --git a/src/journey/transfers/README.md b/src/journey/transfers/README.md index 6556d53..579b9b6 100644 --- a/src/journey/transfers/README.md +++ b/src/journey/transfers/README.md @@ -2,19 +2,16 @@ The first feature you'll be interested in when dealing with XCM is being able to transfer assets between consensus systems. In the [Quickstart](../../overview/README.md) section, we saw a simple XCM that when executed, would send assets between two accounts on the same consensus system. -This can, of course, be done locally as well. The beauty with XCM is we can send it to another system for them to execute. -The end result is what's called a "remote transfer", making a transfer between two accounts on another system. +This can, of course, be done without XCM as well. +The beauty with XCM is we can send it to another system for them to execute. +In that case, we get a remote transfer, making a transfer between two accounts on another system. Now that we've learnt the [fundamentals](../../fundamentals/README.md), let's go over those same instructions. ## WithdrawAsset ```rust,noplayground -enum Instruction { - ... - WithdrawAsset(MultiAssets), - ... -} +WithdrawAsset(MultiAssets), ``` This instruction is the most common way to get assets to the holding register of the XCVM. @@ -24,11 +21,7 @@ As we've seen, we can use the expression `(Here, amount).into()` to take a certa ## BuyExecution ```rust,noplayground -enum Instruction { - ... - BuyExecution { fees: MultiAssets, weight_limit: WeightLimit }, - ... -} +BuyExecution { fees: MultiAssets, weight_limit: WeightLimit }, ``` Because XCM is designed to be agnostic to the underlying consensus system, it doesn't have fee payment baked in. @@ -38,30 +31,32 @@ Most XCMs are not allowed to be executed (blocked by the [barrier](TODO:link)) i ## DepositAsset ```rust,noplayground -enum Instruction { - ... - DepositAsset { assets: MultiAssetFilter, beneficiary: MultiLocation }, - ... -} +DepositAsset { assets: MultiAssetFilter, beneficiary: MultiLocation }, ``` This instruction will put assets from the holding register that match the [MultiAssetFilter](../../fundamentals/multiasset.md) into the `beneficiary`. -## Examples +## Example ```rust,noplayground let message = Xcm(vec![ - WithdrawAsset(), - BuyExecution { }, - DepositAsset { }, + WithdrawAsset((Here, amount).into()), + BuyExecution { fees: (Here, amount).into(), weight_limit: Unlimited }, + DepositAsset { + assets: All.into(), + beneficiary: AccountId32 { id: ALICE.into(), network: None }.into() + }, ]); ``` +As we've seen, the above message results in withdrawing assets from the origin of the message, paying for execution and depositing the rest to another account on the same system. +The full example can be seen in [the examples repo](TODO:add_link). + ## Transferring between systems But what if you want to make a transfer from one system to another? -There are two ways of transfering assets between systems: -- Teleporting +There are two ways of doing this: +- Asset teleportation - Reserve-backed transfers -We'll be discussing them in the following chapters. +We'll be discussing both in the following chapters. diff --git a/src/journey/transfers/images/asset_teleportation.png b/src/journey/transfers/images/asset_teleportation.png new file mode 100644 index 0000000000000000000000000000000000000000..0aceb984b63cad29b193bfc04f2035fd42368427 GIT binary patch literal 9809 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
v4v5usR7G=Vm%N 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 } +``` + +The `InitiateReserveWithdraw` instruction takes the derivative token from the holding register and burns it. +Then it sends a new XCM to the specified `reserve`, in this example, the relay chain. +This new XCM contains the following instructions, in order: +- WithdrawAsset +- ClearOrigin +- All instructions specified in the `xcm` operand, in this case `DepositReserveAsset` + +Upon receiving this XCM, the reserve will withdraw the asset from parachain 1's sovereign account (where the real asset is stored), and deposit it on parachain 2's sovereign account. + +### DepositReserveAsset + +```rust,noplayground +DepositReserveAsset { assets: MultiAssetFilter, dest: MultiLocation, xcm: Xcm<()> } +``` + +This instruction is used in this example instead of `DepositAsset`, because as well as depositing the assets to parachain 2's sovereign account, this instruction will send another XCM to parachain 2. +This new XCM has the following instructions: +- ReserveAssetDeposited +- ClearOrigin +- All instructions specified in the `xcm` operand, in this case, only `DepositAsset` + +### ReserveAssetDeposited + +```rust,noplayground +ReserveAssetDeposited(MultiAssets) +``` + +Parachain 2 receives the XCM, mints new derivative tokens and deposit them locally to the beneficiary account. +`ReserveAssetDeposited` is a *trusted indication*. +As is the case with teleporting, you need to trust the reserve. +You can specify which systems you trust as reserves for which assets by configuring the [IsReserve](TODO:add_link) type in the executor. +In our example, both parachains trust the relay chain as a reserve for it's own native token. + +## Another example + +We now know this type of transfers requires 3 actors, the source, the reserve, and the destination. +However, the source and destination don't have to be different systems, they could be one and the same, as in the following diagram. + +![Source is reserve](images/source_is_reserve.png) + +In this case the message is the following: + +```rust,noplayground +let message = Xcm(vec![ + WithdrawAsset((Parent, amount).into()), + DepositReserveAsset { + assets: All.into(), + dest: Parachain(2).into(), + xcm: Xcm(vec![DepositAsset { + assets: All.into(), + beneficiary: AccountId32 { id: ALICE.into(), network: None }.into(), + }]), + }, +]); +``` + +This simplifies the reserve-backed transfer. +However, the destination still needs to: +- Recognize the source as the proper reserve for the tokens that are being sent over and +- Support minting derivatives of the tokens being sent over + +It's also possible to skip the `WithdrawAsset` instruction. +The `TransferReserveAsset` instruction handles the withdrawal already. +It can be called like so: + +```rust,noplayground +let message = Xcm(vec![ + TransferReserveAsset { + assets: (Parent, amount).into(), + dest: Parachain(2).into(), + xcm: Xcm(vec![DepositAsset { + assets: All.into(), + beneficiary: AccountId32 { id: ALICE.into(), network: None }.into(), + }]), + }, +]); +``` + +## Next steps + +Next, we'll talk about a very important topic we mentioned before but skipped in this chapter, paying fees for the effects our XCMs have. diff --git a/src/journey/transfers/teleports.md b/src/journey/transfers/teleports.md index 0470dce..39e3277 100644 --- a/src/journey/transfers/teleports.md +++ b/src/journey/transfers/teleports.md @@ -1,26 +1,126 @@ -# Teleporting +# Asset teleportation -Asset teleportation is one of the ways we'll see for sending assets from one chain to another. -It's the simpler method of the two as it has only two actors, the source and the destination. +Asset teleportation is the simpler method of the two for sending assets from one chain to another. +It has only two actors, the source and the destination. -The process follows these steps: -- The source gathers the assets to be teleported from the sending account and *takes them out of the circulating supply*, taking note of the total amount of assets that were taken out. -- The destination grabs the teleported assets and *puts them into their circulating supply*, depositing them to the beneficiary account on the destination. +## Process -We'll go over the basic XCM instructions that achieve this, then some additional ones, and then see some examples. +![Asset Teleportation diagram](images/asset_teleportation.png) -## InitiateTeleport +The way in which we transfer assets between the source and the destination are briefly summarized in the numbered labels on the diagram, and are explained in more detail below: -This instruction takes the teleporting +### 1. InitiateTeleport -In Rust, we'd call it like so: +The source gathers the assets to be teleported from the sending account and *takes them out of the circulating supply*, taking note of the total amount of assets that were taken out. + +### 2. ReceiveTeleportedAssets + +The source then creates an XCM instruction called `ReceiveTeleportedAssets` and puts the amount of assets taken out of circulation and the receiving account as parameters to this instruction. +It then sends this instruction over to the destination, where it gets processed and new assets are *put back into the circulating supply* accordingly. + +### 3. DepositAsset + +The destination then deposits the assets to the receiving account of the asset. + +### Thoughts + +The phrases "taken out of circulating supply" and "put back into circulating supply" are highlighted above to give an indication of how much flexibility an XCM executor has in implementing the semantics of taking an asset out of and putting it back into its circulating supply. +The straightforward answer is to burn the assets to take them out of circulation, but there are multiple methods of achieving the same goal, such as transferring the assets locally to an inaccessible account. +Likewise for putting assets back to circulation, the receiving consensus system can freely choose to implement such semantics by releasing assets from a pre-filled and inaccessible treasury of the assets transferred, or perform a mint of the assets. + +The above also gives a hint on the disadvantages of this model, it requires both the source and destination to have a high level of mutual trust. +The destination must trust that the source has appropriately removed the assets that were sent over from the circulating supply, and the source must also trust the destination to put the assets back into circulation. +An asset teleportation should result in the same circulating supply of the asset. +Failing to uphold either of these two conditions will result in a change in the asset's total issuance (in the case of fungible tokens) or a complete loss/duplication of an NFT. + +## Example + +The following is an example XCM program that achieves the process described above. + +```rust,noplayground +let message = Xcm(vec![ + WithdrawAsset((Here, teleport_amount).into()), + InitiateTeleport { + assets: All.into(), + dest: Parachain(1).into(), + xcm: Xcm(vec![DepositAsset { + assets: All.into(), + beneficiary: Junction::AccountId32 { + network: None, + id: ALICE.into(), + } + }]), + }, +]); +``` + +Let's discuss how the new instructions work. + +### InitiateTeleport + +```rust,noplayground +InitiateTeleport { assets: MultiAssetFilter, dest: MultiLocation, xcm: Xcm<()> } +``` + +This instruction is intended to be executed from the source system. +It takes the assets to be teleported (that match the `MultiAssetFilter`) from the holding register, which needs to have been populated, usually with a `WithdrawAsset` instruction. +It then sends an XCM to the destination system given by `dest` with the following instructions: +1. ReceiveTeleportedAsset +2. ClearOrigin +3. All the instructions from the `xcm` operand, in this case `DepositAsset` + +### ReceiveTeleportedAsset + +```rust,noplayground +ReceiveTeleportedAssets(MultiAssets) +``` + +This instruction is a *trusted indication*, it's meant to only be executed if the origin of the XCM is trusted for this purpose. +This level of care must be taken because this instruction will *put assets into the circulating supply*, usually minting them. +As specified earlier, this can result in an increase/decrease in circulating supply of an asset, or a duplication/loss of an NFT, if the source is not trusted for this purpose. + +You can set which origins are allowed (act as teleporters) by configuring the [IsTeleporter](TODO:add_link) type in the XCM executor. +If the origin is not allowed to teleport assets to this system, an `UntrustedTeleportLocation` error is returned. + +This instruction will populate the holding register with the teleported assets, which can be used by further instructions. +In our example, the `DepositAsset` instruction will deposit these assets to the receiving account. + +### ClearOrigin ```rust,noplayground -InitiateTeleport +ClearOrigin ``` +This instruction clears the origin register of the XCVM. +It's mainly used to not allow further instructions to act on behalf of the previous origin. +The `InitiateTeleport` instruction sends a XCM to the destination system with freshly minted assets and immediately clears the origin. + +## Another example + +Let's say we want to teleport an NFT (Non-Fungible Token) this time, instead of a fungible token, to another system. +We could do so with the following program: + ```rust,noplayground -InitiateTeleport() -ReceiveTeleportedAsset() -DepositAsset() +let message = Xcm(vec![ + WithdrawAsset((GeneralIndex(1), 42u32).into()), + InitiateTeleport { + assets: All.into(), + dest: Parachain(1).into(), + xcm: Xcm(vec![DepositAsset { + assets: All.into(), + beneficiary: Junction::AccountId32 { + id: ALICE.into(), + network: None, + }.into() + }]), + }, +]); ``` + +Very little changes, in fact, only the `MultiAsset` we're referencing changes, like we would expect. +All the teleportation logic stays the same. +The example assumes an NFT with index 42 inside a collection with index 1. + +## Next steps + +We'll look at reserve-backed transfers next. From 3a497bb965e65c2de82e1a4e9ba7e7c840c0fdfa Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 4 May 2023 16:26:20 -0300 Subject: [PATCH 22/73] Addressed feedback --- src/journey/transfers/README.md | 1 + src/journey/transfers/reserve.md | 24 +++++++++++++----------- src/journey/transfers/teleports.md | 4 +++- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/journey/transfers/README.md b/src/journey/transfers/README.md index 579b9b6..4f1c6b8 100644 --- a/src/journey/transfers/README.md +++ b/src/journey/transfers/README.md @@ -35,6 +35,7 @@ DepositAsset { assets: MultiAssetFilter, beneficiary: MultiLocation }, ``` This instruction will put assets from the holding register that match the [MultiAssetFilter](../../fundamentals/multiasset.md) into the `beneficiary`. +Note that `beneficiary` must be a location where the local consensus system can actually deposit assets to, e.g. it doesn't make sense to deposit assets to `../AccountId32(0x0)`. ## Example diff --git a/src/journey/transfers/reserve.md b/src/journey/transfers/reserve.md index 79d8af4..3a4cbf8 100644 --- a/src/journey/transfers/reserve.md +++ b/src/journey/transfers/reserve.md @@ -1,6 +1,6 @@ # Reserve-backed transfers -For consensus systems that don't have the level of trust required for asset teleportation, they can instead opt for trusting a third party called a reserve to store the real assets (think Statemine on Kusama or Statemint on Polkadot). +For consensus systems that don't have the level of trust required for asset teleportation, they can instead opt for trusting a third party called a reserve to store the real assets (think Kusama's relay chain or Statemine, or Polkadot's relay or Statemint). The source and the destination need a way to keep track of the real assets they own on the reserve, this is usually done by minting a new derivative token. Both source and destination now need accounts on the reserve to hold their assets, we call these their sovereign accounts on that system. @@ -77,9 +77,11 @@ InitiateReserveWithdraw { assets: MultiAssetFilter, reserve: MultiLocation, xcm: The `InitiateReserveWithdraw` instruction takes the derivative token from the holding register and burns it. Then it sends a new XCM to the specified `reserve`, in this example, the relay chain. This new XCM contains the following instructions, in order: -- WithdrawAsset -- ClearOrigin -- All instructions specified in the `xcm` operand, in this case `DepositReserveAsset` +1. WithdrawAsset +2. ClearOrigin +3. All instructions specified in the `xcm` operand, in this case `DepositReserveAsset` + +As was the case with [teleports](../teleports.md), instructions 1. and 2. are added automatically by the executor when using `InitiateReserveWithdraw`. Upon receiving this XCM, the reserve will withdraw the asset from parachain 1's sovereign account (where the real asset is stored), and deposit it on parachain 2's sovereign account. @@ -91,9 +93,9 @@ DepositReserveAsset { assets: MultiAssetFilter, dest: MultiLocation, xcm: Xcm<() This instruction is used in this example instead of `DepositAsset`, because as well as depositing the assets to parachain 2's sovereign account, this instruction will send another XCM to parachain 2. This new XCM has the following instructions: -- ReserveAssetDeposited -- ClearOrigin -- All instructions specified in the `xcm` operand, in this case, only `DepositAsset` +1. ReserveAssetDeposited +2. ClearOrigin +3. All instructions specified in the `xcm` operand, in this case, only `DepositAsset` ### ReserveAssetDeposited @@ -103,14 +105,14 @@ ReserveAssetDeposited(MultiAssets) Parachain 2 receives the XCM, mints new derivative tokens and deposit them locally to the beneficiary account. `ReserveAssetDeposited` is a *trusted indication*. -As is the case with teleporting, you need to trust the reserve. +As is the case with teleporting, you need to trust the reserve to have actually put the specified amount of assets in the sovereign account of this system. You can specify which systems you trust as reserves for which assets by configuring the [IsReserve](TODO:add_link) type in the executor. -In our example, both parachains trust the relay chain as a reserve for it's own native token. +In our example, both parachains trust the relay chain as a reserve for its own native token. ## Another example -We now know this type of transfers requires 3 actors, the source, the reserve, and the destination. -However, the source and destination don't have to be different systems, they could be one and the same, as in the following diagram. +We now know this type of transfers requires 3 actors: the source, the reserve, and the destination. +However, the source and reserve don't have to be different systems, they could be one and the same, as in the following diagram. ![Source is reserve](images/source_is_reserve.png) diff --git a/src/journey/transfers/teleports.md b/src/journey/transfers/teleports.md index 39e3277..efedf45 100644 --- a/src/journey/transfers/teleports.md +++ b/src/journey/transfers/teleports.md @@ -69,13 +69,15 @@ It then sends an XCM to the destination system given by `dest` with the followin 2. ClearOrigin 3. All the instructions from the `xcm` operand, in this case `DepositAsset` +As we see in the example, instructions 1. and 2. are always added by the executor, no need to specify them. + ### ReceiveTeleportedAsset ```rust,noplayground ReceiveTeleportedAssets(MultiAssets) ``` -This instruction is a *trusted indication*, it's meant to only be executed if the origin of the XCM is trusted for this purpose. +This instruction is a *trusted indication*. It should only be executed if the origin of the XCM is trusted for this purpose. This level of care must be taken because this instruction will *put assets into the circulating supply*, usually minting them. As specified earlier, this can result in an increase/decrease in circulating supply of an asset, or a duplication/loss of an NFT, if the source is not trusted for this purpose. From 36c538ef37b4e471820f44522e14fbc1b5e0306b Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Fri, 5 May 2023 15:03:59 -0300 Subject: [PATCH 23/73] Add notes on trust in reserve-backed transfer chapter --- src/journey/transfers/README.md | 4 ---- src/journey/transfers/reserve.md | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/journey/transfers/README.md b/src/journey/transfers/README.md index 4f1c6b8..f7d2284 100644 --- a/src/journey/transfers/README.md +++ b/src/journey/transfers/README.md @@ -2,10 +2,6 @@ The first feature you'll be interested in when dealing with XCM is being able to transfer assets between consensus systems. In the [Quickstart](../../overview/README.md) section, we saw a simple XCM that when executed, would send assets between two accounts on the same consensus system. -This can, of course, be done without XCM as well. -The beauty with XCM is we can send it to another system for them to execute. -In that case, we get a remote transfer, making a transfer between two accounts on another system. - Now that we've learnt the [fundamentals](../../fundamentals/README.md), let's go over those same instructions. ## WithdrawAsset diff --git a/src/journey/transfers/reserve.md b/src/journey/transfers/reserve.md index 3a4cbf8..c9a6817 100644 --- a/src/journey/transfers/reserve.md +++ b/src/journey/transfers/reserve.md @@ -1,6 +1,6 @@ # Reserve-backed transfers -For consensus systems that don't have the level of trust required for asset teleportation, they can instead opt for trusting a third party called a reserve to store the real assets (think Kusama's relay chain or Statemine, or Polkadot's relay or Statemint). +For consensus systems that don't have the level of trust required for asset teleportation, they can instead opt for trusting a third party called a reserve to store the real assets (think Statemine on Kusama, or Statemint on Polkadot). The source and the destination need a way to keep track of the real assets they own on the reserve, this is usually done by minting a new derivative token. Both source and destination now need accounts on the reserve to hold their assets, we call these their sovereign accounts on that system. @@ -39,6 +39,15 @@ Secondly, the sheer amount of steps required necessarily makes it more prone to Last, but not least, either the source or destination can opt to designate multiple consensus systems to be their reserves. In such a situation, care must be taken in order to ensure that the sovereign accounts on the reserves are balanced, so that one doesn't get drained while the others still contain a healthy balance. +### A note on trust + +We mentioned that reserve-backed transfers require the sender and the destination to trust a third party, the reserve, and not each other. +This is true, but it doesn't mean the sender and destination have to trust ONLY the reserve, they also have to trust the issuer of the token. +Whenever you are dealing with a particular asset, you are always trusting the issuer of said asset, because at any point they could mint a huge amount of that asset, wreaking havoc. +You have to make sure you trust the asset, based on the security mechanisms used to protect its issuance. +For this reason, reserves work best when they are the issuers of the asset being transacted. +In that scenario, you only have to trust the reserve, period. + ## Example We'll create a program for the scenario in the diagram. @@ -154,6 +163,11 @@ let message = Xcm(vec![ ]); ``` +### Another note on trust + +In this model, where the sender is the reserve, the destination is trusting the sender entirely. +It's the sender the one who doesn't need to trust the destination, since it'll ever only be minting derivatives anyway, the sender/reserve controls the real assets and issuance. + ## Next steps Next, we'll talk about a very important topic we mentioned before but skipped in this chapter, paying fees for the effects our XCMs have. From 8f1aecfd930f0c4a466ddea4e9b9467363e1d359 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Mon, 8 May 2023 14:36:35 +0200 Subject: [PATCH 24/73] queries, expectations and version subscription --- src/SUMMARY.md | 9 +-- src/journey/expects.md | 151 ++++++++++++++++++++++++++++++++++++++++ src/journey/queries.md | 152 +++++++++++++++++++++++++++++++++++++++++ src/journey/version.md | 20 ++++++ 4 files changed, 328 insertions(+), 4 deletions(-) create mode 100644 src/journey/expects.md create mode 100644 src/journey/queries.md create mode 100644 src/journey/version.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 558ce69..0db633f 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -19,13 +19,14 @@ - [Transfers](journey/transfers/README.md) - [Asset teleportation](journey/transfers/teleports.md) - [Reserve-backed transfers](journey/transfers/reserve.md) + - [Transact: A general solution]() + - [Origin]() - [Fees]() - - [Expectations]() - - [Queries]() + - [Expectations](journey/expects.md) + - [Queries](journey/queries.md) + - [XCM Version](journey/version.md) - [Locks]() - - [Origin]() - [Channels]() - - [When all else fails]() - [Misc]() - [Config Deep Dive]() - [Testing]() diff --git a/src/journey/expects.md b/src/journey/expects.md new file mode 100644 index 0000000..31805f1 --- /dev/null +++ b/src/journey/expects.md @@ -0,0 +1,151 @@ +# Expects +XCM contains instructions to check for specific conditions during the execution of the message. These 'expect' instructions check for a specific condition and if the condition is not as expected, the instruction throws an error. These instructions are, for example, useful to check the state of the registers before executing specific instructions. XCM contains the following expect instructions: +- `ExpectAsset` +- `ExpectOrigin` +- `ExpectPallet` +- `ExpectError` +- `ExpectTransactStatus` + + +## ExpectAsset +The `ExpectAsset` instruction throws an `ExpectationFalse` error if the holding register does not contain at least the given assets. +```rust,noplayground +ExpectAsset(MultiAssets) +``` + +### Example +For the full example, check [here](TODO). +```rust, noplayground +WithdrawAsset((Here, AMOUNT).into()), +BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, +// Set the instructions that are executed when ExpectAsset does not pass. +// In this case, reporting back an error to the Parachain. +SetErrorHandler(Xcm(vec![ + ReportError(QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), + }) +])), +ExpectAsset((Here, AMOUNT + 10).into()), +// Add Instructions that do something with assets in holding when ExpectAsset passes. + +``` + +## ExpectOrigin +The `ExpectOrigin` instruction throws an `ExpectationFalse` error if the origin register does not equal the expected origin. +```rust,noplayground +ExpectOrigin(Option) +``` + +### Example +For the full example, check [here](TODO). +```rust,noplayground +// Set the instructions that are executed when ExpectOrigin does not pass. +// In this case, reporting back an error to the Parachain. +SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), +})])), +ClearOrigin, +// Checks if the XcmContext origin is equal to `Parachain(1)`. +ExpectOrigin(Some(Parachain(1).into())), +``` + +## ExpectPallet +The `ExpectPallet` instruction ensures that a particular pallet with a particular version exists. +It throws a `PalletNotFound` error if there is no pallet at the given index. +It throws a `NameMismatch` error is the `name` or `module_name` mismatch and a `VersionIncompatible` error if the `crate_major` or `crate_minor` mismatch. +The `name` and `module_name` represent a byte representation of the pallet's name and module name (e.g. 'Balances' and 'pallet_balances'). +```rust,noplayground +ExpectPallet { + #[codec(compact)] + index: u32, + name: Vec, + module_name: Vec, + #[codec(compact)] + crate_major: u32, + #[codec(compact)] + min_crate_minor: u32, +}, +``` + +### Example +For the full example, check [here](TODO). +```rust, noplayground +// Set the instructions that are executed when ExpectPallet does not pass. +// In this case, reporting back an error to the Parachain. +SetErrorHandler(Xcm(vec![ + ReportError(QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), + }) +])), +// Configured pallet has different `crate_major` so `VersionIncompatible` error is thrown. +ExpectPallet { + index: 1, + name: "Balances".into(), + module_name: "pallet_balances".into(), + crate_major: 3, + min_crate_minor: 0, +} +``` + +## ExpectError +The `ExpectError` instruction throws an `ExpectationFalse` error if the error register does not equal the expected error. This instruction is useful during the error handler execution to halt the error handler if the error that started the execution of the error handler is not as expected. The `ExpectError` instruction allows to only execute the instructions in the error handler, when a specific error is thrown. +```rust,noplayground + ExpectError(Option<(u32, Error)>) +``` + +### Example +For the full example, check [here](TODO). +```rust,noplayground +SetErrorHandler(Xcm(vec![ + ExpectError(Some((1, XcmError::VersionIncompatible))), + ReportError(QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), + }), +])), +// Pallet index is wrong, so throws `PalletNotFound` error. +ExpectPallet { + index: 100, + name: "Balances".into(), + module_name: "pallet_balances".into(), + crate_major: 4, + min_crate_minor: 0, +}, +``` + +## ExpectTransactStatus +The `ExpectTransactStatus` instruction throws an `ExpectationFalse` error if the transact status register does not equal the expected transact status. +The status is described by the `MaybeErrorCode` enum, and can either be a Success, Error or TruncatedError if the length of the error exceeds the MaxDispatchErrorLen. For pallet-based calls, the Error is represented as the scale encoded `Error` enum of the called pallet. +```rust,noplayground +ExpectTransactStatus(MaybeErrorCode) + +pub enum MaybeErrorCode { + Success, + Error(BoundedVec), + TruncatedError(BoundedVec), +} +``` + +### Example +For the full example, check [here](TODO). +The transact status is reported to `parachain(1)` if the call in the `Transact` errors. +```rust,noplayground +SetErrorHandler(Xcm(vec![ReportTransactStatus(QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), +})])), +Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + call: call.encode().into(), +}, +ExpectTransactStatus(MaybeErrorCode::Success), +``` \ No newline at end of file diff --git a/src/journey/queries.md b/src/journey/queries.md new file mode 100644 index 0000000..08436cb --- /dev/null +++ b/src/journey/queries.md @@ -0,0 +1,152 @@ +# Queries +XCM contains query instructions that can be used to query information from another consensus system: +- `ReportHolding` +- `QueryPallet` +- `ReportError` +- `ReportTransactStatus` + + +Each of these instructions is send to the destination of which we would like to query the information. +Each instruction has as one of its inputs a `QueryResponseInfo` struct. +The `destination` tells the queried consensus system where to send the response to and the `query_id` field links the query and the query response together. The `max_weight` field tells the queried consensus system what the maximum weight is that the response instruction can take. + +```rust, noplayground +pub struct QueryResponseInfo { + pub destination: MultiLocation, + #[codec(compact)] + pub query_id: QueryId, + pub max_weight: Weight, +} +``` + +When a query instruction is executed correctly, it sends a `QueryResponse` instruction to the location defined in the previously described `destination` field. +The `QueryResponse` looks the following: +```rust,noplayground +QueryResponse { + #[codec(compact)] + query_id: QueryId, + response: Response, + max_weight: Weight, + querier: Option, +} + +// Reponse Struct +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. + PalletsInfo(BoundedVec), + /// The status of a dispatch attempt using `Transact`. + DispatchResult(MaybeErrorCode), +} +``` + +The `QueryResponse` has the same `query_id` as the request to link the request and response and takes over the `max_weight` from the `QueryResponseInfo`. +It has the requested information in the `response` field. +And it has the location of the querier relative to the queried location in the querier field. +The response can be send back to the requester, or to another location, so the querier field is important to determine where the request originated from. + +Now we take a look at the query instructions. + +## ReportHolding +The `ReportHolding` instruction report to the given destination the contents of the Holding Register. The `assets` field is 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*. For example, if the holding register contains 10 of some fungible asset and the `assets` field specifies 15 of the same fungible asset, the result will return 10 of that asset. No wildcards will be used when reporting assets back. + +```rust, noplayground +ReportHolding { response_info: QueryResponseInfo, assets: MultiAssetFilter } +``` + +### Example +For the full example, check [here](TODO). Assets are withdrawn from the account of parachain 1 on the relay chain and partly deposited in the account of parachain 2. The remaining assets are reported back to parachain 1. +```rust, noplayground +Xcm(vec![ + WithdrawAsset((Here, send_amount).into()), + BuyExecution { fees: (Here, send_amount).into(), weight_limit: Unlimited }, + DepositAsset { assets: Definite((Here, send_amount - 5).into()), beneficiary: Parachain(2).into() }, + ReportHolding { + response_info: QueryResponseInfo { + destination: Parachain(1).into(), + query_id: query_id_set, + max_weight: Weight::from_all(0), + }, + assets: All.into(), + }, +]); +``` + +## QueryPallet +The `QueryPallet` instruction queries the existence of a particular pallet based on the module name specified in the `module_name` field. + +```rust, noplayground +QueryPallet { module_name: Vec, response_info: QueryResponseInfo } +``` + +### Example +For the full example, check [here](TODO). It queries for all instances of pallet_balances and sends the result back to parachain 1. + +```rust, noplayground +Xcm(vec![ + QueryPallet { + module_name: "pallet_balances".into(), + response_info: QueryResponseInfo { + destination: Parachain(1).into(), + query_id: query_id_set, + max_weight: Weight::from_all(0), + }, + } +]); +``` + + +## ReportError +The `ReportError` instruction report the contents of the Error Register to the given destination. This instruction is useful in combination with the `SetErrorHandler` instruction. It then only reports an error if an error is thrown. + +```rust,noplayground +ReportError(QueryResponseInfo) +``` + +### Example +For the full example, check [here](TODO). The message sets the error handler to report back any error that is thrown during execution of the instructions using the `ReportError` instruction. +```rust, noplayground +Xcm(vec![ + // Set the Error Handler to report back status of Error register. + SetErrorHandler(Xcm(vec![ + ReportError(QueryResponseInfo { + destination: Parachain(1).into(), + query_id: query_id_set, + max_weight: Weight::from_all(0), + }) + ])), + // If an instruction errors during further processing, the resulting error is reported back to Parachain(1). + // MORE INSTRUCTIONS +]); +``` + +## ReportTransactStatus +The `ReportTransactStatus` instruction report the value of the Transact Status Register to the specified destination. +```rust,noplayground +ReportTransactStatus(QueryResponseInfo) +``` + +### Example +For the full example, check [here](TODO). +Dispatches a call on the consensus system receiving this Xcm and reports back the status of the Transact Status Register. +```rust,noplayground +Xcm(vec![ + Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + call: remark.encode().into(), + }, + ReportTransactStatus(QueryResponseInfo { + destination: Parachain(1).into(), + query_id: query_id_set, + max_weight: Weight::from_all(0), + }), +]); +``` \ No newline at end of file diff --git a/src/journey/version.md b/src/journey/version.md new file mode 100644 index 0000000..70752d9 --- /dev/null +++ b/src/journey/version.md @@ -0,0 +1,20 @@ +# Version Subscription +XCM is a versioned messaging format. One version may contain more or different instruction then another, so for parties to communicate via XCM it is important to know what version the other party is using. XCM enables a version subscription model, where parties can subscribe to each other for version information. XCM has two instructions to enable this: +- `SubscribeVersion` +- `UnsubscribeVersion` + +The version subscription model can differ per XCVM implementation. The `xcm-executor` has a `SubscriptionService` [config item](../executor_config/README.md). When the `SubscribeVersion` instruction is send to a consensus system that implements the `xcm-executor` with the `xcm-pallet` as implementation for the `SubscriptionService`, the system will send back its currently `AdvertisedVersion` and will keep the subscribed location up to date when the version changes. The subscribed location can stop the subscription by sending the `UnsubscribeVersion` instruction. + +```rust,noplayground +SubscribeVersion { + #[codec(compact)] + query_id: QueryId, + max_response_weight: Weight, +} + +UnsubscribeVersion +``` + +Check out the [example](TODO). + + From 8707201965c37e61d22cb25ba8d2b2dfe3f36ede Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Mon, 8 May 2023 15:14:43 +0200 Subject: [PATCH 25/73] match with examples --- src/journey/queries.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/journey/queries.md b/src/journey/queries.md index 08436cb..39f6902 100644 --- a/src/journey/queries.md +++ b/src/journey/queries.md @@ -65,13 +65,13 @@ ReportHolding { response_info: QueryResponseInfo, assets: MultiAssetFilter } For the full example, check [here](TODO). Assets are withdrawn from the account of parachain 1 on the relay chain and partly deposited in the account of parachain 2. The remaining assets are reported back to parachain 1. ```rust, noplayground Xcm(vec![ - WithdrawAsset((Here, send_amount).into()), - BuyExecution { fees: (Here, send_amount).into(), weight_limit: Unlimited }, - DepositAsset { assets: Definite((Here, send_amount - 5).into()), beneficiary: Parachain(2).into() }, + WithdrawAsset((Here, AMOUNT).into()), + BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: Unlimited }, + DepositAsset { assets: Definite((Here, AMOUNT - 5).into()), beneficiary: Parachain(2).into() }, ReportHolding { response_info: QueryResponseInfo { destination: Parachain(1).into(), - query_id: query_id_set, + query_id: QUERY_ID, max_weight: Weight::from_all(0), }, assets: All.into(), @@ -95,7 +95,7 @@ Xcm(vec![ module_name: "pallet_balances".into(), response_info: QueryResponseInfo { destination: Parachain(1).into(), - query_id: query_id_set, + query_id: QUERY_ID, max_weight: Weight::from_all(0), }, } @@ -118,7 +118,7 @@ Xcm(vec![ SetErrorHandler(Xcm(vec![ ReportError(QueryResponseInfo { destination: Parachain(1).into(), - query_id: query_id_set, + query_id: QUERY_ID, max_weight: Weight::from_all(0), }) ])), @@ -145,7 +145,7 @@ Xcm(vec![ }, ReportTransactStatus(QueryResponseInfo { destination: Parachain(1).into(), - query_id: query_id_set, + query_id: QUERY_ID, max_weight: Weight::from_all(0), }), ]); From ff3d1b63a052be2372a9e446fc69ac0f94a01cf0 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Mon, 8 May 2023 16:11:38 +0200 Subject: [PATCH 26/73] add example --- .../images/MultiLocation_Example.png | Bin 45421 -> 44567 bytes src/fundamentals/multilocation/example.md | 32 +++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/fundamentals/images/MultiLocation_Example.png b/src/fundamentals/images/MultiLocation_Example.png index e234a867ebfd5f89badf7f271788acf874163e32..11cd815afe7a65b1e3686ec43de172e7a9ca5796 100644 GIT binary patch literal 44567 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 literal 45421 zcmeFZc|6qZ+dqsXDiKMPLiU}pWZz{OyJ+kQlUelJM!uHc(G-nIxZ{%+O|DdR z(Fa+PvD_A($T^xa?#f|yLwkdZlurz1;{AB$DwjWj$lsmE0tqaanBOTiAktKiAb4=h{+X*{`^UOJ}gEg zVdx|=G4ZQ(_&qJ2e?5TJvd)Q#o_>$WucgrQ{I8$=S>fBoR>YH!$%!ogZ9(v7ZU#|4 zWLukv=wEBSyBUf<|B|0u`R}{`{z_ez7z$rbBdFp3kqi78ahnLoaNW${$v-8I^(RT` zAC@2{`p*{r{)+r1c&kiJ?)iT#qQJaODul+kkr4fRye*vkZ9a&rk6p|Ii*$*f4D?BEOjeobEq1tKbXX`t1$7-9Hu~BEfKm;-Nv{{QtGg zPjYLi_WN4h|5${G)IgUAhuY^LBKp@d|G(n@-&K5TCh2IY8T{C}alL~%?9btmssZ!7 zqDk}U&wsvG&QgnEtO(rO!1i7hcjlUW>w|0#I?&A1Od91Kj-unW-RzS<{} zYOc+D_Txdjli@`d@V_sq!aHEzv>aR`zf~8u1qAZj#}{aQu5nRC4iq`0OZ${TGh|>q zCS`ngij3lxM~ZurOf0z<(>-4Z=URmvRR@B{7fw!*osX42{-x}{yTCuGFaC8i zcHp8r(Ehz}A1Mm)&J$G_6vn*LJ_ed9sv$mZOUoePnm%5O$@hH!>>{?>$>hGKmu^9- z4toBJ|4OOdr`PwMv_AaE1V1*$)wf@e?o1KBA021WW-b1%YcOaA@b| z=Bm_8dYe@??*5v6>66a(;VHpr!h{I7K1(z8j}T$x6p0p=#T}bVIWLoqOHI3CS(Kyk z=~7-r-|J>ytf2a3cfS~35B_ye@>>Kg)WK_SHQ-0n@0ULqVy!GO4i|VMJD+HnYu}gZ zF%!w@s`oZvt^Rhu^p8ezCvD^3@A!I_7+S!qsju_LpD0L@cBksZj24>+?#(6W_BQK? zp>g@z5Drn#{7SQ0S8Tl}yi(pUT%h`BZ!y#E*UxX+oL`rt@nvt<9D~Oy?6mOXl?9@X z!|E5LzYA5C8zR3u>M0ioD0z)0{&k;RT0}UFw7;(RmX3nHTLEde3u#R?l5 zjj4DIJS7oJdiB3%p7+sM%8Kpw2(~1GD=MuMKl3}hC|m&!_?{lS_TQaGkXB6M{3aX< zx?00d^~s{8aFRj$2#6WVPq$!g_50wfa1>^E5M#(W?p)b7hcCz!ugpC<*hM0(f?+$K z?o;h;k6F#9I+eu1htSkf(l0m0^9N{9}`y9f&Vg-vi{k&Yupr%{v+zhAC!7TA&@v&fx@KkjHw z%Nmc(vv*ticq7EwUQ$;I&xQdf+T2Bb<4>ewPSIf`{{6E57DpopK8?|o=3gO~h#1Q0 z@~;R-M5;sx=al>u{|e`!MEMEg|B9YOB(Dgm!jt5Pf29fvz&x)0D>4&>4T8NWD{H;` zdpQp%5<8^~J7aR=j~?em{4r!UV8{s-`VYwKw7!;8G5PQ+ba~QnadUSuJS88Zx!dSb z#%I1Bb67DG0NXe)hwUGbz~1=3*{k%Pypb=I#OuO3QZy_c;^=Cu>*zA7S5q{4x29|q z>S*F}#U-qZ|1wFS+{Z_J&>)F9yvg zUnY-da*%*0QYYE3hqG(Bx`p1TjweYXfo$ZndVA{IZf`Mfr2mKy4;Rz*icF%TvEY~} zZkGMa$}i#1*+cPs$;<;lfqoai<{{|?7Fl!L8skf0wVKU)^Gti*>j5H8UAHS7`^v*E zKfuM*#&T5eBr`ew;O(oCQV=hza>&9b+-)RXYFXc)lJ?o6OcTPJr~s870F|<`w`vI* ze2IfUX^y)o*yld+PQrG7z^$|Q!wnUKXZh799Hq;MSf<)FYK>r98ad*9w_nk-y~IJY zxw=y+IupQ*sJV$P{o`g{$UwGYuGRNih->5@ZWTc-el;W}9{O?V8Qsf?;px&b^GO`_ zqC!3qn@~yO--}QQJ-$kFS1VrCN-IL$L0AaQO_$-Q%q&3CxHZFiL%VtDDkZ$n&Z5f{ zwbkw^f`7VbH`F~oWS7ofz+WsLFaG)1aKbqt+GwKCqr{G7^Bu)t$T|4pyM(2cN19#y zH}fI&69y3SyBeX$jHC)uW3DaA^LCFZ@c7D1eqT*FD4F&(TY$u^J2m6kcEq}y>ghnq53yA8Zd;U@!jg#~FVk50r;@+1X0`-I1>W*W>@`2U zSI0ny^2bfA=32B17_|7Wuo}*+bAi3&+xU3Q<0>aH$^Nq$8KRrhtgk!BkHwha5a!*ydgQESs*V5#%%h_vy9 zM18EGOE}q8CI78y>MYYlwtZ7JwX@G3;i0dVj2K8B(tORM=REqksW$zUA*A^$gP+sF z&qf;Kc1(z9FHA~bE=3x9$Yk;>0QcN>*ZbKxZ7AVYkpGtd38`W(^ieU z`zPIM^s^d&xN@O+Mdkei!b#6FdATlK6??eRj=+)u=kW;$@SHM;jUmTPC}pg~j9oB9 zR-xHqE2EsWwzxhG;d=CAd#0l)SKO@4=aYvOemFbR+1$aH{}oJoE0AD+m?M-NF{>BWTv8(Qw|`&&iYLDKyn=zS>|O*Z*;)6|@|bots$ zv8zf{GSQKQ>6HHBlV*KdPWe9${aFmS0-pwN#|!V1h40OmpXoM{KM{o6jGr%%yR@iL z?TOSb`8k-K>~$eohrg@`Cc&zr3$C~9CYPtmG?zny35?`BK35w=kT}AjbFXzml&5Fn z1nREj`a<*t7QUrx*zSNRgWAlx^qnFT>BZdBm?zH*dJ?qYk9w?o@;1jh;yB|*q z9-Gs|4zl^g=A~HHkhJexStMn*RA|B35L7B1_4?2ULpXJHyvV-cSS8p4;gZ zQJtRAYmr~P=oixe>=8|uz0CGR;w{~Z(n8h)=P~V_@wMJZ%iYS~z)qgDk0cunja_EP z!Y)AJQeFk4)X!o<&j~hB&~kJVpSkw>dd?HdtKIsynb?(|UP$Vbm~J!=-v7S-@rEGC zukY(emp5)`GKf2;1Rkz`tQfNlpi44NqmMF8)w~|!u7K@i}iTiOsZ@m(2AFvXz zdQL~M_l(&Hp80mw(pCG*xoA#e`hBJJo6gT z`%41A57K!=M}b_22RnC)nEVQfK;u@UV&Cp`h+vva4GMq~o%B|LEQEaVi|=fV$M%Q` zvUmYxv^UXGUh#H4$#f;ZK(Sy~Tv{VCkm)n!Ct}+f`v@1cuSgGWiiX@?%-)O=s^zdJ z@cKBjD{L0OXq}lY!uRvr{)kUh4o{E^yG>PPc|tK>%K zO*>FpiFkj%TmSXZIhCHO>EMHTUX8hRi$MtwF54>%Aeh(|c9D}%yZ0f=?!Hkf0(m>n zm#{O}xk^o|;}3qcSOx$3cBXzVUd#6I*E%*|`)d8(LYm#~qz|$p-*YfaaRVg#-Kkwpt4~yEg88=!anzB7^kQ`Kd+KJ&p|Z8!}-9OAPy z*p@OpQSGF)*pHr{3iu(`<4FaI2gp^^CVwx6Tkctn$NMV_pFJoG(B7#Gom+U^?d4(2 zeXRViui@wi$^uJHyqz>KPM;v(+`~8Csza*1jqRI9k~dXPNG~F$3!gA!Kj(Ug;fL1P z$JM`EC0`+xzKR_j*AYv%h)iL}% zU2@y$pwg%6#OF9bZgz!?mAK^1md}lyv)dk-52Rx< zw=uMs5zvb5acVEr8th1zufA89D6KsiAopcx^ydn36ujh`ZH;_41Hr=V7w|dgT7DSAAK( z`*M6Ka23^w4GYCubs2#hT|`Ob9Naqdj}nlWP+NS?$0ybrGc(>X|WfEN4#UD z&0U{ke)ZajN8W;oc_lvBqNBWRUKVYq4gZum&<#`v3Upf?k+FfF^p?JgTd(U@%3G$c z@xlM>N*N0OcJ|&sY|1a+5ZSVv#BDi9Yrl;!MfVw<@+FkV$2w);iy5(*A;;t2*N17{ZvbRtZ@IX9^k#97=UW7xRSPK~ z7JmKnqgq?7-P-8jSY^L>W*N!<6wxVdJ#z`wlJ_x7jw;50hrG5ee2 z5VYM7{BZd6)^n93o57v0?+(VipQQA^qkD(A-2Z};g(!@KoDHA1``N7pHLf9Rq(pVn zb$Lsmc5ibr?bOq%wR7|WqLB0k!7860Qwu%~w>#(cc2uC{0=*s!<;%QQ)65{(x_NTu zW-x2odhf1jvay#`=SBW~HS>)v`+~}JQ1RDn?jrW@#11S{YOv#vR%)3dm|o6TA5+|y zI;v#l02x0KD6DMkiJqQkut~PVSD2xL++}U0vmU|v5AOtp^xxb#PQTcEeb`B#ar9H4 z$rmA3zk*r&eDurpFYvdn6$9SzC;FmTzF4Bnfv&^AozzqNmp=*f_@2k%Gr99|^1L>{ zYY~I2fzuwp>QXz&ahu62@%x*P*NOFFFNoWeqB5UVAl<)8HIMWQDi|rncHM(UGx-@&lE=QZ9O`iv5`93e23VFt3rV}6Oo*E zBnf@sK!d78_j+9Bz|JIj&aVshBd5ps22i1;`M3e+Mf?RzJ%BApm_flx^ema9EwZ|e9OCUL2&$m=^vLlCQq!3bgDu^% z!CYf$N!NljPu1Doi?m-iC2hW5ggN6+S(wJ5j&gGS+ji9~+rnnI8P^IHsXzic)uzHtE(yLdH)n{iI}2!gnb} z=L9N@K89VSC7~t{1qmq;vuQf%{$;`bNArgBcy46RvR4az=3RB}!f!^i_{V|g;Ypfl zT@&fOV@EP#O%L1poa8|VG=|MP^Tx_+|SaY3W>q4Z=v4X&O z_GmnL<@U*P#(J!%63c^+t%M>^r)vs7F?^1#=x@VfK6nu$qvjqlE1Xb1%5HMVa{D=< za!(3CqYCv~SgpRMG5?plif5SEsN{ZSrTZ4aJ2rLTJ@*v-iNUTY=PigMGm`aEnr{93+C=<$*y#K!6lYSUX1JK=Kw> zCHd{DI%CrjNw!vlI3qn!y(q?2L728EEsXN=*lD zYQq4!ytR6;IQbEzas^8k)lL4}@cyCx)CNyhYIV$ihiqnuG+T0G=6CV++t& zi=%H(Z`gv+EHdz*?r@`L9ze%&2WxN3w~A_?3pA*Mpv8eS_t4O(OX*L4_6$H%qyRmz zj1^tgIu{f^WZTzpXJcdR)ZAy)AEy(ni7>X(bzD~M_3gBnw8Fzzx z+}R%HHM*H><|_PtikrQ85yN&4*pKikeMMm)#&Mm{441|Lf>cnw^0)u6;J z?R@?~WlQu!Wp|3paRxQv;fEPUX{gn2eFxny%zU`A*<=_wfMup2W_7Ou%Va)_L)8}; zI=LV3E%wAnOg%WCS=%U%#5)fc#zkKden+Hts_10e_3f%H$ifzy-NjxsM2~3=K+rjA zF{W;QrTtk`Aa4+{>%IgroXFFnhgCg@BV?tL-)y8jSy@SduVVw|s)(%;|vV%d^XdQVaRqJ&_>v@&aU@d+!seP zn)EG4GeDx(CvBed_x55kL-AY6k+*=#tO+W6g6)nNl(-99;6HeGWk8h|ekRR541-x8 z{<(u!mPvY<+*(G>TAKlP`LtcUe z8YE(h7`zOC;Cq|3EwVp@%y4L$>e?+mfYY*zIgN@|CI{O~iMh$@j|Cj(AuUA)_9#+x z`Y967&vdZ5z`auk$sEpV+Z_t$Pdeb?Id3n~s_1Bw_RhG>;B zTXb_FneXgz$Q32p)+TH=S1Eh&EA48{%ARIwb#1gzr-R5+zDv1EKdzSobAv! z*jcRa-S*9;3_nPsKyAIQHO35Q?tqq^}mRdg0R2}$%yM5y-v|tJc15U*AWxhbj z2z#Y=ns}0h_jukgBEx?f<1q?@GZ2N7QHm6dEsIQo4v`7Zogy|`4yaH7q$xm((0AH9 zKKKFPdBK*$3PRV*;sC9I{6;#sc)vmys(6=q!}esPQPc4}UW zV60Dbi*EC=UEnQ-h(yQdr4v2GF#}1dX(Q*j&%{&41GTBA?&LjgZgZlk!S@!t2f28{ z>2sIWkM_6c1;b6&W*vo0$}RanJ6FckpKKOn28)~s2=bZL^nRJdgaq%+(_;(^UIZR( zk1cf5wGiX*$V=-0(@_AJ&Nc}iNU+m$p6_bs%?`P~r<6pM-u(c+p4LkB4h)J0d zK*Uf?yip2pg!iy9@-9GGr_!MCpVgm*KD@dAG1UnM+D)?xz%m-TFI%(rAuqt3mgl5iH?FcIjnkP%kI%*teU*yxYvCX!Atb9 zFXRM4eD#}vE`m;h+uAAF_4l+|xX{g!@k9qYH;S8b&Q$ZDjmk0)7W)f7N5R&aKSpSk!a*XSvkcPAA%?0OpW( zon(nW>|_fhKsF2?QK$CFbZ`7S{h}vQd7hv!?fU^%>6#R{*(1HNIe(B*UlT;V4P=NN~6mXnxe1T-M^EbTLmafX&1KCkNkgntz1@ICIz)KKO z^XrfVtzLw;ZD2&;DzvPIos3 zWL|zC&-|S#6NNQW{Ot3X>hvY_pA^_gXZ8bM@J_2|amdh$p%^D>-Q14*?WMNiQ$J{5 z+$>+dC$;%b)t1mU+q-D(9(O~~hR~jR7NuaGIq-1)D&1J_BE-6th(XdLmk{5K?wp)} zq7&6}u=~U9I0paaHJ!Co!W5+N25>RF#ANhuW;lPdUgl|@t!zq`1v;EP2kuST5nR1Eqa>oWe2*Gg8abV zUK$e4sDnPTO@>Irp>X%rmx+Xiw6j8J+vyk*LK_kNXOC9WcibzDrkvq0To)gF&)-IB z(s?D35kO55MB(RdM`ROn-DUFk+k4kcnlZGu=FYx5P#HBVTbT;^=Igi#8&wmDonGhj_}?{IU# zP_s3p`adajsKOLjeJ&>Y3}LG(N`F?5A$5Ewf)aIz2krLp;UX<0WTOZzSFD)nhO;+u z0DWl70Zx9Ae27Do!405C9+L9{CGp|!es7HZk5XBiAK~DNTEGodqSR_wXBqXpO0>vu4rsPZ0P9ny0fwPrkk9}`(O~@}35Ukp ztd(p?V#cdpJMx}>`y$D0d)4jfc2@>d*LBe;I+GpImH|46C8-Yd^?Zoy=}ypwRTl;8 zL?$D!k@t0?{2fTfegY zZv7CE)cmzeml2jV53gHQIoyw|a+dlGyd`J1unaxDr=B)4;LM*Gz-6}U&G$O`I13Mj z(TZ_wOoS@}19qAPBPjHB!k-J`RB(1Zx!cS*t>>5D`mlP@c&`_Yi~Zq7^5+FeJELHp zwZ}&US$muj;T&z*TfaVfLQJr69g>GZavzh*(LC?Xc-)dVV;+_x&8S;Cs+>vot_+y> z_|N8Dg07|XTi=R5OXxZOZ6}8+lg@~QJe*&N6^*02qYYQ>v4+5J@`~io(|s~iGRweh zVF#)&Nr*7B*hk>cUlhN{3!n*G?jyTL?%C(<-y&$sAtvX)w-^l)A_iT8 zUVdtawmo(;Qu%G_+<#5opzeb0LZ8xt>AvX_U%^x6vFM`Apu7uTdtoY2xQ>bnXcRH) z-2!L(xN=s5pyzvk52@W2Z!=M!+`NyX*f#uOzDFvYwp?)S;m0;aM=8-B!6-Sv$qsE8 zNCKVS`>WG-gU7~OQRg$^QsK8?HI0FfL~33UpB1_Xg@+1hoF!ac7_jOGznLuG+5TAL z1V*VbujoOGUDM=}9jhfwxj{{@SDG5edgwUDW*#?tU#(Y@Um|&j1w<`i$eeRT^7p=CZC}<3w4*pxEFp1z@Pc# zX|S9*>o>5>S459UHdL-bjT~yO8aqP^%>4CD!8U~nH=Z_n1|%yOmOtCfSNkNxo9b|D z#91d*1}1Yq1(lfkUI3o%+MAsjD;Z!ajuwBr#*?G=QYUHsJ!0QM>^)(>ddy%SrytD< z6BNPn_qHl`;B7bN`MpLm0~ECj$p)Sk36C%gZYV7T_d&x=M_(IPA%`_mB+tg+q1i>l z6yTmc0lwQQH;b9TVi^4!8dy)BkRGm$_li5=`P*E8^Go*htvZFzWsRZu$7BACjKtrO@`QweX>(c#91|WfSeQL&4q{f+KJ)i|0Y&5#q5|9qta50Zs()2}K3LK0Z_uh8rf8`Oh8AR^ z2BS2W;d^}f05ttp>+J!=rO^e?&#>n|qEq7^TL2>vp@Oi9r4Nd(`=cJ+VDT3;RK3 z{&gDx$zu)Km*#<7>1?fE-)W3SnE_W{pD+f&Hq0z!&$x z4|9!_MwPJeTm)G7XpZJFO9>ZM%WlLz=K1PFA#@F|${z4I1r$Pc?xu2Qsltpde%<6@ ztWWT^Has~#%Cl+-9%?;zi~a{<+<6zVU>dwqz*TRrC5&cef0({7e_LjT;CkF6iNbFo zhqFtl)g^#v*wPS|>Kj7Vp?>++Mb3(b5og~n*J~x@k&TA5grerp>VJ3g}mvFU3|1ISPgk-y}hYs*pQv5d_1LJY# zY-R5Ee#`~3rLSO*Q8fUZ8q+Qqt!2HBKPnrXFQ>~xJ6#sF;&*=e5?^J((tU=Q{FZj8 zVmfX^-iLTRt zbFe%XSjf19!+h^aOX5RBy7&PWM2};WcMKl!=Bc4Q@EHt1d+BjekCXs+_*bK55N+pG zg?K33Z=oV~TxZ}e&tOFl{JBL%TCIArOenreg**xbLAC^<@H*RN%~IxA6(AnF{)qcZ zA{!1C$FLP#;N!RaN~!*lgj4D&QMRZ>LVOnm4pltzGl1YsRH?10O-N;~KmN_xS-g{5 zM;iwN?<-@_Zm}00+kk0*Eceq^xw%(NlhV(I(XarvwL&iRtWt;OEGaR0qH^c}Z>m}| zX}qme^%@<6QcHmJQEy5GXl7Vimc!`L(6i2Yhwd44Cha^*H3e%LN(K+tpy zS}g&>yr~zr)1D;6u-twH8t6tzQXnN61#-b-E?pu8VNjThkDEJ;mNbx7Jr%!|{K@|F zbClCw>6y~(af22uM9{gfdMeNYWgC4Xpg&naY}pmfc~7m*OEBD`z;D|+sdyurd6b&` zQ;Iv6IL9mp-raGZ)qoaUmD1M)zt9I7zee}MMw$6BLF2EMRE;Gg^KoW2X5Q0zyPm4a zE*wT78Gd&(7dEH(Fdf_hk2T|HZf)Mm$1z~IxPbAog3SpOsi9b6bqKa$xNGBxR>7eh zsCz1g4B^|m^Gu|3{F(eO9T=&oH||2Vrb!2K{&69i{@ z$L;_`(E}2kj)Krd<_#f=UZW1AA&+%7H^$8=uj5e9sapt#s7DGOS8biu2ag+o$5o@G zFQW2B5JkRHvnU3HhD$1PV8L-PrQbZ_mnnv9<~5%lVM;eKMFd6do(8agLA*4Vn^Cp-^^! zwg?|JaC3+P;e!XF-D zw?y+H+Kz@Myc9wYstqomJxy!{-rp=Fd6gmvoz!pAHZ(?`zs(wRLw@S81&}Qk+@SE| z^SFJ|d`M}xp_>yZ^()oRo+SZg^pH?aeKIL-|t<4th|-%;LC_ZktJbrMhM&Er+OT&(C@at=Ph3!6EUzj=U6NTm`&T; zJKh_TcV&8V3jjN(;ob0nKY&R~kk3ezLQ(yRHNjQfSE|M@L()iwJ&>Jl7Z1QYFF^hmR+16h8t+mb;Z%3g_-x-Sg zqLq&mbfmftuh6E-f6kQ;`;SOK*94KW%zx|p@2|?e0kgxU-oDaV(@{=1V{cvC-x&(W z(&Yz_(pu}?Z&JBL4J`M+_OL%Daibld7qz}MAVDcCjCRmNNttQ`KHkWN-Ys$?b)fU_ zWRIGc(ws?-9VP60uTfX{*j0nFJxQ903sdak?(GO^3g6fP3d?R~efuh;y}2QghNYPHFBb9D!l zf9ExINgJWhFwu7^Y(Pm7(MIp=fRfCOUse>L0rWHu_y;{>K7FZTBAyFSjA?bzFd!=c zlzGnlzQS}A`#GCrTu;~$y5H+%>$zzB`D@%1O3diXGN+B;`cWpsePqA+S@YUxUR?2v_Jpl0mIh1p|r}<#*s*!kH?reN>91(Yd z26bgRN<$9t2_71S%kZ=ONf6>J!8@?UvSL023UCl5Z&8@mK7lYKJ{{=k2SDyzi?`kt z{@adzpbN+1KqiF2Kn`$X}6`Kov382kz7tDRg zZT9B8l;e@A90zoH1}Q}0A;9b_K3q2I1alyKlMPdpKHqzs}py;C%sJIjE}YU z)hM?CNB>e22YY90n35tEA;=Fc2uRhRi6Bs)g%mM5MZiTT@YibX`{#Rpse5RTDn7PC zLNYx2B}KfJjmGaiWwL#{S_A2^@#5G5Jb`3Dw&?-gZF_s35z*`3pYCD77y|*MM=T90 zuC<&5V=Ug*AlAOX)|z%L9i|%VzgP4Kh}`cu?=V_|+MIct>o)v4{L277i3!J09pyd$ z6ki1(y96b0PzGF5cXZAJ1GLHQHNA-30%R$BRRm^*z$$49*i8o1k*;6sv-4mqBjNEm z`fe~%Fb`mMdNjiwoZm+#pUy|8&-I2WG+grm%%_g@ezf#hiVk|!=w^|YV+i1bjoYWW zq|;Bc2jx6rdV-%5dR2I*idP<5VBxQqdI=CIkxD}t4;!Ek+M9MZ(8bx8@&f*$u}{E! zkwy~Fo!%CcQd4$`FYe)AKxY_C9C^@i;&YoaQU&U`hNMT^Y5L9tT{-UC*B(Z|;t5UGl=+$g+1+$Q zN^k*k$gXxMDw(5b{%_~LhVPYW60bDaTpU22x^S!Y`{Hm`!T2)1T55I=9C%t#dPBN# zJ7CBN5ZEnyy8udd>77UWOmWk4>WD%z8n(PUV_EXLKqKzef{K|%#fkw?z^ytDJI!mu z32B^v*zC7KQ1(>eilPYP`yEg(Yv5|Z#1Mkp8RSpBBU}U`OeOkGvGJsW`sAr1wL7ys zXT*xlT#FKkys0M>)LI@r*)fjXTNylFHx|AqM(-1SA`rqFUJR z&`|G0|K1sX=?uAr-+YpY^CzJ_FaTv&-n7$g@#dM$gUM~iS4`fY+RQZ&d-KSim-5GT zifg-50p;UbvVe5ynKwk34`Q)&nW(xrX?9TCa?~f2)AME9$DjEVfn*5`v7*X(TqJwq z5|T2%@>Z}6C1{oj6>>AOP$)2qgK+bQ+^qIUvIaUhc4OZr8h)wAkcYUF*w*)PT#7+c!xD(#!Uv@;R4F>2>9PYOL9QXVPGi_c3Nk*9yI)#%nDO7F%N~pJ zjhs`W-7gP`+G895Rsuu7N{^YmeF3dC&uOe|VfltE%x|jEFR#a_<=4zP$&$-ADiA7C-xKzEV{WYi-0t}RD934ooUby?8T`Ft*!Y~LOj3{0?W^6wgl zXYvES$y=GDE!@H~i&)GFARy$0oE&!uEo1sYIv)!NSFeTd_MobfHScsqJ!NP4ZVeRz5YL^n{?BYlC> zq!ARRg9-ts`RGIS7X-m=+bc*p4i}z+)@CytkrK}h> zu0Rl9OSOo%g9p&w6B-EEeZcJPy)Pxb37k`>#B|`zGfaqBbHtFU@elo+lLbentH;5; zDZ(4?X?2Z$Sq}%?(v@Bn#h8?50OH@rst@27h8N_1S>xn~2k}jnj@?f#F!6Y;jEX%G z{yZ>pw}@pDIPp0`_#p6Hf&RR48QU3VO{Po0fr)zkn0#9cBP$;G-g0vMd&ZIXspyM- zz2@yvvwW!kKC@znA;4qcE{-u#V zwf6ZmsDpMMgQa<-RWj1jzbmEF#L`dog8ZR7t;OyK3~|%Xcjx*nMbO+>RO;*tX`L7H z$G(uxk+zC{A^iCiflVBHxYW-P#h-vA$ty95NMMLw$(kHhIN>*E5YuMNhoWqbM-R-r zj3H5|zFq@CSjGF^9lrp{kvV+t%+c<&+eE>!6`Za3PO*L2uc6Ep%T8zcF+t5Lst0Ce zmQ4*^`5+K22$;ES(xLiaFiCfNjak4mhMIGp7#uwC>v00yJ)5=Gk)T4xDmMo`9R4(V zUd!Z}9Wm+{v+zlzWI*S>1{ajOuZx5IV^KS~DjM{3P(d?%THqxK{Eq zp2+wl`l^J^8rTcvdu-U7+FU^#A_L5KF39OHWjWQ&MDg0rypOcMY)GYo0x<@~jUA^) z!~6lsus75a{uFR@?`IG)thvmSLpVNkx+B@9vRQni;th1)uCwKMTW}miQSEh*#!275 zviJm8h=^-^{xwz#aJn{RalHmD^~uR=)Z zHb1;rTSd&jH-i?$$UYOvivs@Hs7u?97F2)4rq>Go!v#naQtUDMXdeUt4HB*G>mi2^ z{eU)j%xN&EtSveg`w+_tW@iRdpOT>|R>#US$3yb9h#*u-LvXLGGBrd;G=sfH85x>* z#4c(dVo~wn{%hEDogXGxalrIRTzq~W}}h6wW(#8BR1>Jx$IBZB(Fokd+E?!CevQ-V@5@WZE~b>xo_ zo6_?6h6gFv1#-y@!h?x(^9fPV{s(4_`!^Bi2co`WZRHwh!J;OOUNr3Dk_?@iYNGMc zZ%qDaOvs0}gKKf&`C`tJ_zF<_5#LC0VS}A^fxrqb$W4lCG^@wmOQ;M22|q(+ z>9(LwB|U`72W0fY^MeF-RmM=*+l)dCBv&e>R`^qin|b5#;1WIcel6U0%k+hf(s)po z;_BY;xEJ~x-`fIx%2#V4!#I#w9)m$)@dF;Oj^O$3BzrRX&jVf@R=RB?O_@@k%Xvww zO8h-a_Q?$`>p5{*fO~fyvEBraI(YNsiMLU5t5bkh|D4Z;Tl8Mdf@+>!tShyBLgcuRV8t_MmP|IRt ze%`BRk9JRNxt+uzr+GWrj1fQegz=7h!_Z}V7CXQLl7ox6G$73TQfpw84JZ2Wb0dwI zw%#32vA6~#47s$ds7T1V_6+QHzK?fYBBZY089?~Opt=U^q`bQ7r;0I0N>t~ua(PGa zwnwM2uawVtPLz_NC%=Hf*GKNO2t@Aobc`c}v zXAZ)yFa@rNm`&m^5P^n6z4C}blwQ+QJtX;ZN&fZb?4AePsT61p>^O@o8uxkDAuRps zr8fJZoUchgCeutgCaLgfqqNfKDKDO>4fB?5^3T5e{Abqx>l+%a^kG#kTY-wKG^Bj& z3MbV@DWOqzmCX&JdbT40itNwJ4m zPj!x{vtcDb(_OAt0K}xhZvC0B;3KSDiUnZp`4Cit0q;|Cp~8pieBe2e-_Owyo@3MN zwCpdu_4>Uy4|ZrV93Pz_DXC;F3lOLW)YR@^Tx`r@^)T^T(2<^pgpTQU^+Si!v8Y>m zos-9j#dCk5QzeerQKV((UCevV#0#Z;zcx2ln}bj zosEwC5|p`Au<214pj+^|38Yhzv|ko*(CQ=D>m+L9=O!tXoW*K`;ev&UgpBibXJzw>`UQs7`R$_Akd`lQ%> zEbm!HD&dLj}fQUfGd$yu2X@zULswe`g#y6-s<6 z7j&wUlTr!oC{5s)%DiLM4C6T-;|k0BILG{5UeY=zjGq%mcMT_*!k`M;^PO=w?ZBrJ zIs*L#Wsm$otE=w@wBQ4YL6s56gY&hHMN{KNxzp8xEQHY4I1+m;pW&hTWy9SbBq3fh z4%pntUO@6stN|lb6}DY8+}zx68n-S{k>EKLPPAVhJBYI5^-VNewzLh=F3kcTaKt!N z@8!tb*g2JMzjolH+S%vaNAE ztY1Y-XPyQiK55LtyoH*K+8R=cN0rGA!(%%u28}x(tGT;YB2)V395iFDvFLf%M8I@z zN93eXcZz)0IMKGRRL|ko$d4a)N+Rgd!SS2-2p!G=c|DQCj#LD$l`;=S{W2Q!1E;je z-U|x@TTj=cxy0hK-{5W$HP%>H3{hlSEiVFWqXKR*k7q=jqf%^_RUa!bfTCqiBC-k< zKgZh;Oo_`E1pO)gFJ=2SQmfc-Y;LT=grz@sVhRsu3cz#PX7UjORuy#GwRpt0hD-KR z37=1a0~r||PqmZIY9H^``&mMIS2O*~6U7tlvKq@i>~M&~K-D7K#X*$L(X7&-li?N> zizL9|yM{!B#qKRI3me#%lJ94`RZy_K_1>C(u=N=9IKhpl2E_e*h}7uZTmq4xS!UL? zm?zCI&MsDdT3Vq^%o!S#VEp#=8YSEV^vbirhXJBrh)M3Twf2m1a^22(Ri5*1k57Dm zpLG6#=vIu{EzrV6nS=Jk5*YN)xv#AwNAa`bzvh4Cp2sxHDM~U@2IXA50NSyHxie-d z046ToO-RrsX><;HtM@`pdHO*_`2R=6q`Y0XKKoG%5PP*~1$+Ej zLy58FRxR!#oOcL)m5boZ1!_bnz72rEa>!ejeNeL+yVO(OxRiE7aLgvh?N$^9LrBC3 zT(}h7mzBqjph8@r%7~L_4_A9Gq<#ce0h= zki>6krhi`Av={4HXyt?Z_7c9u^xv#3obi9KGV^bZD7}Ewb z*dW#23u>xXXH?SC1IyIZT4G z#sf@_MFDc=Vh>UfnWu%+i~+-*vtI%?`xYwa!B<@3!C=Dsz|lqk+gGf2Ts>p(!#q;u zm0Otz+tF_N-89hcxj~90L}i`H(z0r&a7HHc>%L#Z>|Ym7YlZ;S!edbJOcxka;ew;R zjI&AzL$i>t!)=9(g8;sh8r;fJ?-dKmv5wD`$sXJV@Nuj%m8{WazPro-Vu%MKYrEEc zBl817+5*%g?8<8Z-)aJ6$qp1P1_e4yxJ4kNlxOMV1%P-~Z`+!I`4E6}-W@Ixe=yjZ zqH|=vG}UzAzdo#Bj4@Dk94+B~DYNkm0X}@PwYv`mkab@*1EcOHXsdAMc`o+nJ)Ed= zxS7DIn+zt14gI&4?#bOJ;M0WhTvi7){iUlKs(b^a{mJK42;A;Q(9GOa>o|3f)_v8k z_|yj-w3G(;-izi?y8Andg=t;+qpHB2ak#y=3jF58|6%T}gQ{%5wqZp?P>@oPlnwzA zklKQDcT0!TB9amUl1iw6gfwiV1*D}VRk{(72HAv2t03^L3;li1^US<6?>FE3&pYEd zx8Ab%d7al8YaQ!2j#cTvfrwQABox1W{KuslW-|*G!HK;0jl!!1u&#+WZwaB8A1}Ep zhe$stK6se}AQ=PT<;2%9cL=gNK^#I_N$sEWBeB8%jJ!vH24*!u)z~0ETPu)~hyULA z%b_3ui9q0n;*cz~Sk%BoyWSh{Yo%vRxyR<0!3fLVbwCN1yWj{iI_I9lnKvV82=Z&Zx zG$g`g_xC1&MotMbBPmB?m@O}6&d=i01Xq1_q8@Lyk#9larxSF+-SMI0ETdqTHC}l= zrO2?V2yqB9D7T@nT`LV=gRHm|3ORQ)FCeT5@efy_6fCCN8Wstg2$t8%TczGq(e9&b zH1ZftG5(jR&VKM2av7^S@{KP@e))Pw9YM|ZkWD*n1LxBv`+PGf6dJwVfXbv z<$?tEIzS&)8i({{X+D_(WNGEDer{uIKW9&{_;~s4n{Q6jK0gzf3lkrVrJLd`lS|j! z9NIiC3t3vxk5-AotZI<1)C{;Q_DsHTRpH;~H&U73)UdLuKK3E<>x1 zw`uHlnnUR;TSojTQ?vu2q^y*KH15wNr{Ap-S?`a_?sm2zcwM{liuEC=Z>*+BQCdY} zTHkjsVEd%y?(HW)A!s^@n^9*8$$9^*T|NHwRDY$=G>8bHI(|_-HE3Ha$7*)^U61gvgFMVmZ2}xFN-STnu2Q)jMI|KOTIy&}Y8-pE;K} zrrvuepJMp*)AhwaGl7I%5#3!0eBCm0*UygbxP=xHSU#6D?RB*p(X&2w-7{55rIby1 zK6LuljSs~R`td(-pBjEXfP=n4wdW@clqgDQ)orhfc~YI+=)wR>=9 zjJ6bh+YPvIolQkgjJ(`&b1vD8q$gepTb zeEe9tTc60zg=>=s%L%H_3%cN3bP@(DD=9~;ll95aBR&!BYo764mEarbAMM98drZh6 zTh?xT?*Us^rFLzkFew>qvPuv4Cdd<+dJ!)r+*k;7wCk8ma8Hlo@T1-jH5FbI;>QRM zC*&xsq-$D>?IhXK5MfTW@};0Q`W#F7F?8BLvzDJ|T0eAsmp}cuV0p-~ZgsTSF?G;? z1x93!Ggxy^{an`noVonG!9T86>IY%u2ttMb4FDvigSpFkMjD_XV{wHl(#`(P)wI;* zE+dnIbH};n+Gm^qW_kP6uyYga&Jrv)DV@2BhlNmDd1Y~lsi__Xe0S_P`8~3=^i(ke z=Eio#X;%rq~53Vu16U!C6MeOGtWD$6riz5l+)28YHKN4uA( zk9o@Ow%c@LPbuliA_5~J+gFq^ZA1?p)L%KM<*+USwrp?3v-=^ER86FRZK9v~;h1uc z@7=<(WJLP>TfpcX8j!BnJSnI%`LH{4cUAL-rt3lZhzP`T3|`ppqt1>7G|Ofumh4*B4}MO;lQ_Ow(u_;MA7BpQqEZP=qK99NGX z9B#>jVm!6G(WE0QTdO;8eSVO;Xs1j<4~4N-&0ZdQ^T zheo@UO~DAxYFO!ug!NBEcsu^e*FW#uE7&gLAIN~QWEhi9orP^gHKctH<#rs~6R>hW zWPQE{gAQMSQp1fLQ(styR_)C&G)p%GBN4`xi5~|)w%O9pEJ;`H=1oVg;y7PC;CTaj{o@PU>W*i9fW1Kk;D~-YHe|!AZ$-x9q zF9)kbaP!+80Q953B5nCHqaE39cp|{#RD9|h$3y!0z=vEh7fTpT_V~R*``{2u{y92i zYkhf0%9OWJWPn0TRCJU>OJFeX&`>4sQE^VT4=DH(nGe0T^46_&&a8Gvhm4V-`vkkT zo+ecK+U6=HyR{pNP0wFMc^Ca4K1B0jR%_>qs1U04Iz(?ZIr?)-$*mI4*T0WBcXC-G z4vo`($CH29y%z({7X+FaZ{_gO*F@-A>$w8UFoXHJU;xv~YALNm{3Mx=+sks_s7xP) z&NrstY_4{>7e1Q?#V|*T?a<|OUhm_3B4jV`1Q|}AS*H=dh}9Rxxc9=?k3O zp&bQ`i(~quRCuvcsavJ7TJZEC#>VE7tQlHA2;u>8&uVZbFck*FK^c{%u z)RjN^FeIpo}?i_+i17ZD9Tf+2)uq>U!SrL7W?535Nt>-9W9CzTIk=MGPs{XW%|4 zzlaOX!wXDol~1T(IN=FJu8sHm9S`Xd64ZRJoLGP=Sc$WOgJ+>SRF?hSyb6h)USO^* z*1U!L1-$5NlNe$?R(qpFKkw|wAI?u^hQ^Em@kzzK^P!IEesQDwa(=^+-4b`4NjUoH z+2z<<0yLBh#XA=3-ovYpcYc)W9sg1Z^gw1}3%u64;;(EENxw4)ov1Q;?uz zKYLw%?LDu}@S+FY@bw~KoH+2_=`P|>Qmv4Q# z4B-vLuxq^m|DfNu9~eJ2X3o@G$9PMtRWL)!OoWRpc;%Wm#zd#}a0>6XM}QrQ+nz>z z%Su%n>I^z$8`ebpQmS{)*9l#dT#d|--}Qok@y2+CR$YwX$0+BT{3*)T9URtI;l`9b z`s(zQ2aTE;w$!AATb4U_G!3Hnw{%D5V)x1#%tu8DmM7jiW-oKTIqm@wE(DECkerl7 zX%fEbKcl}Bo{r>#JJUge59?`V#JqPeguDs5?X&;-(S^}mCK-*D)2F==&gC#m1+$A~ zA0ojni?;`7Qdth;{5MTMN(;bd2&|N%kPqE?A!Z!n4XmLKgs4;VMyWA&uQ}CwogFc9 zS%ANb0jWH~sOTAV{g52%= z;b=u%qNyhm<8508PK6uPx_|#eTPal8z~>tK!q>bSf-6HK%`lwPxbGn{U9Jhh<80&z z)a8F}hr3HhOaQhf;Yi#K>)TkeK zR&s+$j<`tYN~eL;I}D$8?;GHCl}L`A%wzen-85gs`E=CCS-6iIgpRv(?fb?9H#bhXYkCrj}W<%EZR7lPm}Kx(KfA-4oi-1sXYcKEyMQIgguLg57>FlzZZ zH!9`tYF2ROV!fV9{O+>g+J9;o$+}>2lIHo1;`h#=nhM1ZlccCCV%1r$s#h#7uxihB z^XYl3vWA50vdE(X(?39IYFb+ztRJoE_;6vz^eLt7X_-REtj6R_F(zQziU174=hA`O zn~jYaxaLGb5K8)S*TuL|oYR{i+}HoT^P=K*#DgXq5ccC!3-$ehi%5gdP`OX%=KhQR zEn+0%3zC7$pG<#%>-7_OJI1;{!IXz`K8JN2abiUf28zMFpJ~g^%rtZ{>p%K&nn)&Ll4g?YeKP)pz#ZTI8jQggpaJSnW2D$#1jN z92pWa4Ujrad#!Tawd2<=HknNJD&XU%bj2%5)FI$9D8EsbGF9&{gGW5jBCtxO8LJih zwtJs!!Ve5v=LQZ36VNMevNg)lM@e1TMF)OAfti6`U(_DVYu6DQ+;w%Dq>Soz1v}=7 zY%WiUM(rW)OZDm_uKU8}Juk1-$m##^?{Pm@k+u|yh+*i+p#lTNsN2q10)z&g&3Ax= z!V~B9C@ryxE`U}$L)HbHsEoOVMd|ej%laIv-Pm=l`WE%U(x!Z`b-b&z=8YqfS2hYy$S6libx71CSdb_NLAg4~HnEc^o` zQc|mb;tbW6hP~xKKBrpxNj!&SpBpN0LQ-8O&SZ$Jlu*$&a`?>6+j;aKF2Fe-2MvZ~ z=X6IQBL@Y3hy9iMWaRd5`sp%`PD*pe_@sEDijGuHqy_(+PK#}j>AOVGRf63&%G0#P zd42yI9G4i|!77;kZ$l3cZ!MbxPZ3Fh!~ehjs9tdnutsC6+pjH~e;=wylhLm*|<5PL&8#!y5mNlV-`X_&p? zzU`o3nrKQ5ryFG0n&3)-c$#D0W+?fK{ErC=a2=zhPe4kE zL&F6+<2$=9UWJL>5`j3Mf#afgt%3FvpVyAf7#_CP}2kaHxU+1H>jzK4#7m$xo!SP>1bhhc)ckkxGB=6U2o-L<#sDejOAB zMS3+j)+_=|CJum|>DQf9j&&Neo(sg_7Ur2F;`J;H=5GOq0a)Avz;IXL#=DoE6_7>>9|gBqf~ zvjahC2hF3e^l85PdDJdlJkCJ3LBQpX!`}cqT0)rT49M4gb%B)0p~nCYC`@O&fPFYG zcy3p(7S*mfJc+>viCf&M>_PqIXv2+i0=wvfdwYo)j-{97Om|dBPN%T{0q}A$<0);2 zK_S?IXu6MCk|*%o4Y_GQ!fB6e>6{@~Uw|DE1_I)))IFJ&a_f;mSrpI(p430_^*`K4Rk$--#R;Ft%h7aKu<-&>PHe{K+@y3DzVEK=fV&C|oHzXb9?E z@4$n5kc6JC{g~LSFL7<;$qAwHbCL1*K60d4MWiKm-k$}xM*jaI^*Sf^K)@MF`(+T! z=M%uy9-xqkys|gop--0LFf`VWFozM?EkkxSXS5$RRPNaVC^({_(_3C$>;M>ZfAuH2aUL< ztR5aAki|D%7tim>!Y&KoTh8+oUk7fk)=~X#V2XZzXWp^^i|3}87NZy37z%)ucOF)! zxzCQe_N0m$?v2}X|Axi*he?SVDI<9TKmQxZ_nTQF+a*-KC_5-#^>NT-{8b9ZQt$_! z=ld(lOTc+&Tmm~c9L=|eRjP>TP0dXm6*PIn;htgHS9j0>p+kCZ$zzZT2Oxps0P^3p zx}herDQ!DhQ-M@5tZ=jm)(}&B<=i8~pB`2RzY#LCX?Jt+@{mskK0!r>)vLz7GCkxwAdEK- zcwP$4;p)FL^G^l{)T+90y}Iiw5brX;sfNGP#%=il6w($4l(_lNom*~XRZV8|nhZ-+ z6;-#H`QWebSHwEFvQ}{YV0iKU;@QURvb!H&I0NXOSYUi|CHyDUKq=4_Id05%j4DL2 zmG5HP^!CeRFWCrrS7S&F`CpFhX|AxB*W5dy1_uum=g1Nbl_-v%nKwoN0dX0JF!xtfC@k+%e_0sVJE4wU z3vfJLWDp)eE}TffO<#qa_HV>(<$o5=VH)%lO!#ej&DB9ACROpr2w~Xu4;n9ZC+THm zMARn#{#JY-Lv_=}EN*$F)C8=ePWius)3Sv@siuecnf*Jkwdlc<_D`V`_kJPEf~bD^ z;^A_BwjM=&(?)$K;c3iWD93b_P6Gj!;|;Yu8EE05QOAvxa%|i`GIT`}Mn=CGM6jFT z=Pw4XgxEF3m(DL!BZG)W1~KN`f586}HYwkHo?k85j;~tE>`>!R=uTQ~Ns)IBJoX=r@)mPD5!n9XH`rvJ_4-<8ScQm51trfXgFmnbFKv z!wv$M(LuXi)t=7lwaPuHRnCbpIZt>k*s%Rs?il6AUG;yD7sI(#L1bn$!gcV-m60_(0zXnN`7g*nHjCa_8P8a zdvGAn^rfrgz5=;wBq1E{U`C&0^LL91_FHIHe}O%kDM1M5#8OhYNscpIZAuVzTLN@3 z0PcW~U$LQ%^h|x2efD>v;M!GKe?Nb*NQOO;4G&I43FN^fqHdDFgCXmlMe^n~bDER& zf;?F8H(rkovt`ia0ApRNsAeT!e$Q)Vs+(RalN&rv4TRT3Bn0D| zkyq%``8&Q*ofNjJN#q>EoPqI?zY0&aEBJ;6dqbsN9{%x@6wbFNln<-*! zNXzad`MM8KR=EN}2MNdDl|#jGpI40&8P@=^9VHe{?2h4(w(eSjmB@m0b%B&jfs)@& z`{7oZb#EM0`}C2{83{s8Xr3g1U5f4l>HTcZ=RhPuXa*G+{;TV@xBl&S6!XPkw~$lc zg(>rZjk1*P0^Ww!UF=l=qWE<;^g_T(!Ih7>j5y9XsX!0ru4yImLh?lD`YMXhpjEI}36^ zT}2M4E~A~k3@8T5L*{qkH~96*SaT06UpH+q0HFbvp41K zc=6nMtt$hdAeIEg6#pzoXpQ{ew^)-)Y+0FxJk4|DY2G^nCn~BD$HwWh|6{}r0$UMI z2{u&{mxO<(gg!^d18!t?$C0`__LbJl=SY3qHkCRC%L8pg0qYw79w_5mHu`4BR9L+;6=0s$v^fG0_uf8#Bk-sn`rPXoeLu+ z{;wfLY@g!2h#xL{7ojfEMKZyMAtL)Qh7V@dY>%-T=YBCS+Kx+T0$+`AN2Ri1=py2| zatDq9^C)KHCEOdYuc`9>=R2d(pi~}H1o#>zz8{V&+0LY|oGkEBLQXUP1}`5L{krEK zhr)N{wvmPgITT!u&ShZLv^dx5|DRc4SF}LADrBlS*^XSW9n0%JcUxfL$Ay!g`_I_l zumSt6GWzd;%3(nLTP%X%csK4`SEc^HWkA=3s!pH5kq0i|1>e+jhs4mLWs^MW4i(Xd z`|pR@n*S(gT&*4^+hXTB6uD~93Kj@3DbUDmFsd^Bj5Gn5^SQify~MZt|56hRt;U*i zEA__<>I)xPT1P&P449)FbV8hwbp0K&)F1dLDt$cpO5Luly|chV^pl$5Yx8}6pOZd& zP*#VH6nU(_;`mQ7k*Cp&l3DLwgSU#+aKd7FBmG8#MWd`h9MPa{=Cv4biFxwm5Z>mV zebBP2bY?kO*@iHEbsf*fT|^f33=Sl^R48-Qv7fg!mbKR8@n-Q@*PV8f!B?*(2&1y1nJpC*B;ziL za>a!(^=C#CALf11-TA`&eD7&AvjQd~XRd^aSf7`-^yVj2``Yp7QaoSrt$N6lK@uxj;;WBY(H@hG#n8sv{Jb-^6Gct=4+sG_B8?g8` z(Fu8i=xNWTYQ;=GyV+Xt;p8i;AMGV%#RBB@$yQoRzmOpliNF^+SSX!5^~KJip3?)J zwqg^PtLDsp`QV3$Ub1FA`YMFgGo|V!{QPcb6f#J z316|_7LV^Cu`kPqhzehvp5>Fze|GZuuVL}<#I*1R&VEfh%sp^ybFbn~V?V0Z3$V@> z!6PJ$|N8aM%oxcdN#x_1X2@ageVf(}m>x^)q+~S8y{l#06y&b$wtjQ~FRtoVkJxH= zw|vys3$;u?=8B_}oYc3j)5O)Skw&MvX>M)vGINWWHn(JENozCXg}>!A?;mCo$qosM z-LDOb&tskC;Xf=>@A>;(W*o?S<{>|M&t9nS)@&&p*_a``7n7WFbW2|aJ`2}MIQKlz z^xJabUNN{Ex}iy;Q8czFR;3_)pV4^!9VuSqDZEJW4VaShP!)EVf3`5y0}92ZFm{w% zz+FKlqEEe@k!!CCm=1;vKHsia-E!%ns3{^LE+Ns+crI-+tjoYr5l>>y6Y`?B37N4c z*lRLR(1R>{X!yVkw*NjNELA!VhtPXjT(N6njeDI23>;I;8cxQ{*NT!%zs)ltQzwo! z!U$_eH0iw&F>C>RnSNU_sbwP`nH<%JYh*XDgsqQR6F!nF=#J+w{PsLDmJ7WR8s;so zcvSdQI;M8G(Z%KO9`b)envTJLk0)XXJIOlvt!wP0Ola&K11GMNH>X=yplqP!TXc`s zN00YqJx4V;;p>~B)-56NHL8jxgNBN_%UqfaU&~bM!jLDD|FCzmG$sEn&GK5dlB-U~ zMPJtU7Wvv5e=4P0b=JmQi}y`qO^|zarUE;DJMQg(9=c#J{vF0csW3d8{$yX@cnYHt zHX^th%l#~bk>5wWHk@Se^w?vp+KKqyOG(pDXgS4xHealcK}nUW7IAK^JlpDKUh0x1 zl)bqZovTR0ggUat;~wn$;x9~j^4^{-!tyleVJaeTC6`XR(qW$9PsWTiFD!>v6GZO; z-%~@xh#;nMx^R<~RgQs?(Gs|B&U3AJ%Tx8WNU2$d(hOWu2Hu%VRj=2qo;Uu9AUrQ= zMHdQ+8=@h2%}YB(OrW9cr)iL|o_l~Q3zDv>zbPD`=^X2)kYw~=W zIqxr5L|vYccl{&A%&-?{G1_$D-)d4qR#W9Xat2w__g4c$_5`;C%#VbOjAeOw8R?ax zUt$pqq+)3X1_mR^Vd*76@6giLA79Wceyv7#vLl^bc%dJqC%9M_Ux64uwbb{}i`k+x zj*9~+mm2hfp}|lho%q4dG9#3OwSA^NmuM7}m9-_yp>+(>bt8bMH%*KiIH8(8H?nI* zf6YJ0!0eo}?8$g~N+g-$9IN8qvSZkqNyhLGuaMEAQ6de(;&R7={sKGM_s$b`@GVq*)p`QG? zw^uJ!RS)d~+9$-8p!(r#fC!+YTYwqmd6QaH0`3)^ocuWW(e6;5mU9b%Ae$QNH(F?= z15F_10U;eqjJs}p4UXO$GsI(ZCxJ7YOSSA6WXj zjyr>I-QG0HwAvcRKrieiOpn`bN4%JL;hW^G#(9Y<+TPrb6sGL1EA4_v!Y^v`cWR=$G1~ds%jd5d znEpAHs=VWNA0a)%>r@1=dI2F%tbMK4LZi{=jRPwPzy`bF_cyA=@A$44%6;l5#nRZP zNXlk+e^)ziez@{78mEm(grgs?H(w?7$Wcd2u)tG0oFwJWspt!_H4pI1#uM!bE~+9& zl0Ut8KgE@G+V1o7;;t`fMGfk0GG`k%n=ig3716vu!c1hNfeuxbliYaFfb`3O4wj3b zMSS+l0X9~Cc$)-GI@J@!C|7eS1w%?seqwY(Nis*w;QnK(z9Vq?dL7xQ56FI-tZa!-bYA4W6sI|Q-Jl$EzJ-8-R~n8AX89)zUx-q}y|2z)2@Sn2R*v=Hm-vJp;i=nN zJL+X}X~|`V3v)+HqD3vXI{l%j)GWLD>#(Z6oDtDe_K6G&3M1)XFJLpAOt6oLfXuz#%1(*;3u|4bhrrcvUU49tIg*$Eh; z<{J#sbEaUw(5PB7bOO@t^QDsvZ$?T0kIHjNk#~K=|F#}5`VNj-hY108y`totUn-5) zVPb0~V8GF34;%j&DNGA0mma;}>ZE>DP??Wu^~x+-r_ZuivwB?^>?nNwIdVJHLA0~X z#Up0=V=dvs&W`i`7%nR3LOC0|s;fKT3e4LHw|#0zTyYnVmc)+pq_Yb6QsgPI3A0#e z5l5ZS(9jbM3s3FOG3cMp_t$uAqHo&ti9??j-QOE6gu}fMgR;WTt?sZ2FMDI)!iccOUE5>oQ|yGI#_F$ z#0wM=9b6VtTSg5ccW#x|w%6YU5>5gOB-fleesL`!J$?{JmQ=$$-M>6mH)k*<2dPVM zfij-$H8kYW{rtoV;4Y-2FG-X^it6JRLsm%55VhFSxx#mTE{<5r?Kyk=@(l2nuNSL z-io=GW-vcI@HJF*==+d#j@}uR@!IQT`Inf8x|Ij)#*gA+aKkq(Db|!umW>%PoTy|x z(i)u)ca-}&ZO;T6n^>gFS+_xEjOWhv?&`Q`C z^LPd1&JcTZ-$&IyEa!U{Qxp36d+E;&*t4plN)nA6E z?wY9bC4G~>SDH@L8pnb8UPwt98y*GY?{^v}xBt}$$Bd%oM*Qfvq@8spQL(+Qc1fZ6 zUu6_WQ}QaWaVG zDQ>H-IF|=WMqm21h%8ne$^tRcHVz3P7nm6;qx2?Pemf5x%Baj@Fg9)>)M&!pfQc%{ zAH#Q2jUqOkXpg{@M$hQQJno}OxA<#3yHLQprp8+OPzoCnZXj*C)Uw~tB|^c*#M9Zj zXU8O_Hn9)0>FN-c0^2Ac5rQLvuJDLdn_mY|gDY(ctCl8$&^hCBIE$GzM9w7CkA7yg z@G;HLU>hfGGDSre`OBPVCT_ms)neZ-m@OMEt>-;nkYxl7J9usS(gTzyFd|U7)A7m! zRrEl<4u@MdwIq>=9Fv+7Buq=gKkBwscZH+h&^H0Qxt;~el7*<2i)!qpj`QPi#&y7< z9kVe}Zu<)E(OkYCLQCsk5H$xBEM6goA$?1^!VzWPF@bAek(&vnW(xX|#k0ZZT-Lu< z+gDr%)iq@ae&mLoSmJu4;MebVlBud{DgW@ z4H#Z{fM`}u4mo8n=gW6`vI}i+|47I|2!v*7Q<`lKz7X5zI_$N#mUlZFw<7m6JF3wB zN}#TlVE(V(aL~aCL)q`15TKK|)p|cDel~KIA(?9Km zH(Yy?1l5qv&Oprb#>V>$N|wBKaoO)seZ5d>$|#4`ih*MXG4Ieb%NzR-7eFyNBZjm? zes=oK5R0s?;~)IACath(M%;dw;JlLAStOub1i`en*wb9M`z0kM9rNQkYFFdEpDb;| zSzOz(^*reQhVRj;>9OYL(V4c#&^QHK1Md!Z(9I(=(bwcY^d_4{>4oP4N`U<|Q0ZWZ zbmP(vdC2rAiO>3l>*|Qg_X}9Npz2Xym6>*I_$@nZGt%DT1yWXs(QV)qkR|0e6N$-w z$SRQX^rINvrBoboL37;NP!dPtg~dg@Bka=qp;h(*TVA)uAmV1^a(rIm%%{2V=Vc1B z*iuB%*~_u@y1h?cP&hunl)}u=p){5hujJ8Xitc~qzC6I3Am-PA=x@%BRU*10ohdw8 z&x%sun zqdFd3brEc06&{-wNFCYux(_TW=O6Vy$+}*K(`nBZO5e`hGgM#1exrNlN~#L3>=02q zA7)t3R0r(>pj$`Fv+dXokM;2irRUR`a)gLcN(2N=o)RF?Q&Q4O)eP#hL%Tr1;%vp! zaMYmZq}ym7(Kv8NFB^!pvPfTTwx&0$`0Ye2QpRR|cJ_5*Vt%t{KZ|N|uB7K?mm68i zi&Y5*=AcPQbt(DGrdOdnpnE9(_7)aQ#x8J)bVMT-GPRuMr3_p@~zPh@r7;ym7U2*1oRTl|-7gPU|q9M~Ro~ z_v0_B#70OtVy4nlq^*^kT}V=H3}x1@PuA+CgiC8)<$6O#v&k9g(fR`$UItT-b`5EY z{Qen@FuoWfG?aB;0jHwhz8{Z}b!36vMrKyV|%EGM( z7Jm%pZuKN78#WA7Tzg@jH|Ysx@rc99GI7teqv+n!%MxcRslmftK_0G8;R#xcRVcIe z_m*>R;aN24+^cR%>@_;YFvdkT)Mh|{H@+qA218&x^Sx)SRqMY%DM^_i>b-Ya$}WM4 zoXbGEU!VHn8NUZv*5~Wka239mT84wh?aK$_eSslp6Zi&HV_1%tB*_q7E5ddP`x9_> zDvw<&JJ;n&uH(T=zdur**&7EJXPnYF#coD~(wx}4;LoGO%ap7n^NYLZDZ^tRBzCOOwjaKf$BhWQVdx<8 zQhfU)MBa#o#S;IatA_gAA0LMu_kG2_FGo!*D0&MzI^qX$v9VbzGqVlBGp+i0!0I4d z&imMT)g%(Ld~@%%i(sNoDm&3>k33m#AV(DA^6d~3jFx?H366J2_LRz2ySFQClkMWN zvohZx_nxt5RKiSL{sUt$Um$K~(5DYSp2fN1a32zp$yRMo)GG8;bJaZE&A|+PLp9Df zaqv!fmHI#X!lJpC&75kKU*sQ8IA%NBJ=CmZPW6R&wyMn&wdu&-dL`%@HZ!F(Rl~!; z*D}AjdQ|L)>5wD~jQaFD;<=0K5_?o?tIAO+8z0a=z`ic~x=b_OSGhYJMNOXC&+g#r zSi@_05J^wI8t`7a5oA=^eO?dOPERS_#8pmKN&y1Q{pki{n)na(VV-n7QK#_kQ-2J+ zW66jFHklAjP^xHu^pN#nD!27)(0Y1Wd4=RYWP4b3A3od-&Sq}kSWspy%E8=RmUZEI z8w-L^nf?ZEHv{e>r_Ju`4#RC@$G_>LmqP)>6}_ml|V17Zw-;6Yc=y|Sbj!)nVK5;66`x|UgL^3&kKjb(> zwPu_GSgoK8su<)00?vFg29qiJo|!^_HW?546#~f{HT%@f1ID&^LFbWXt}|;?bx|7& z>4ezRU}IM-qVyhTX)==dl6}BA+}(?_v#k`U?m_(+y{jfSgL>wEo~HG~Raqlg|x--l1ZOv5I0(@Q#;AUDs!fhv6DX@6J&j1WDe8c+Uu0UT`28A z!R|NlAc|x^D3jZzjQcM_#RJIM^I6Owo359{jxFPw(KGRPp{jLtEwu=DH`j+@h>Ux4 z$JO9RdsPSD*(s`f#KS$#k_$~`&-VQ{p3EK3Mg>`)GogDt1@T*a8dQ469pK^0$(WHA zT{$9AHlI=>yvb?RAI(;zdhBY*Z3355i#~bd1B28`5FY9IV7)2O*<4d5skltw@Y~aQ zf5IazWQYSr&w!v$W)E;XlFR%;6+L+A#b-A;^Mk@tgY|3@6#MheQ6C`C=o1-$*l@lO z<$eq;W0VzOxx^cteN+;l;>zF&`RS|?i)7VIVGR%&V=-TA+zRr!R*O!d*m&!7kLwpy z)Zm2?Ri5w9vV^DlLa$HQvGGA_`7zGLUA#%3^`dfY$-6W%tRz^ow8J7PM6bu|AusoK z(weJ76a@qvpMkkQb03Sb*TmK^1B3rggInz?KAM!4K3PlBhT(h$V<=8f zTgI#m3WYLAiER;9dov}Mfc5A(dQj|V7VOjwgOMU*LnKP9@Jwc|g-B6*L^&q-139_; z&7dF=p}Un3z{L7b3@;HqgG~!X1S89o;S~zN0(4T62Bld^u1L_KJsD8pTPuu#?22ka ztZn-c5F;{(YpZP1Y%5Z8)N~?f+$R3=vp4E=x1AY+MqXuyYMV;4w%Ci(cOyZJ{D<+A z$GFO8LX@D)_Cc0_p?_JK1bw=hB)Dyox$7N{&$gcVF!$xRTgnpeIYp9jePr}W3MGhS zWn|ckg?l{EGon2@lAmTTTnzdGyThUvcE?T=`Bl&q=->qtjf8C{w1uh~J>}NL!M~!; z=Ix+i;=^RxB&_3sstB1r%QV)!|Iq4EmS*IAT<@UQ0*u}cbR>Ao-I9iY^{p7?)G|r6 z9agR=Flm2%ee_2-HK`{{`;V~L65P{tr#WDAgt(|Fs zh!!17lt+i~V=@z${T!9De;qhI#Prd}GP?sBd@Uo!MJnc8>6I2kQzNNEJ&!;7m2ZSJ z{|Lk4pFCOo7P_7i_%}k*lc)00Ol;=o%(QeXiYHUqaixM!xskc7-+rWLfMuylM>ZS$ z=(?Q7(0BibcY^j&-i*6myYwbb?w1qXyLTRB{qQs>9f(_L*sfP#TJR{GGDzBRx9grz zpU8b*q*>F0Kk3b1%cxj36Mhlif5Q!l*FqwDY@fG4CGxud{tGqau=GW+7QL@}s#dZC zgmKDv$dY!{(Xb#u?U@Gae22L0@CTQg`@`iv?ax9xdfi5%D#e%`-5YlHE&A$(kHdbZ z4lFUD%yUy6_Oo7=VOoFCl|Gh|zYUEQPl&Cc+KkO1-F;3R@O-=R1wmF25zEEdFm4Vu z)S(4R6)G+>a*$;B27P~UtsqA5-Kns2g8<}}N?e?=dV3$DW-O1pzie0DzT#l^`DvQ* z*(*^HO*(In)+ojddDC8UiT@ivOY{nWp_{%dgOfG@%1k^w8b`auetD>pM2xgESIiFy zO#wXzFvH59;*`lTRsN^|+u)BpB!MsAO{xS(v;VRhde+V)DXV_d3ZEmK3q z(4t6EOBTu?u{Z?g>i{T=u=@4Cda`}+~9{?K=KK5DYdvn)6aa-bOhWt{G)Mw1_G zC=HY~bgw>(SMKcMqVxXkw`I-;=9g01i069YyjoL)C;aRzFY7O6#q#8#TiKOa1dTFi z4ACWH9$~NqFU3iC#xL6qTUNvI7keWk0!izZ(MqnU^V*K0v9J3y%IA*j@%o?VMKSdc zr;b#}`MEh?N|Ad(9ffogO+R`YHx`E;G#!}{ft94oZ1x@_?ZbP|bmCw5%X=W}OfwYL zq92ZQ7)J;|I?7+GyA$~2biuq6V&Q~jF+Y>6!RbneB*jxtTr{huyb4wV`Y8V3^J5;$ zp81Srl%@Fd(i7rTJQkv@m`cB*vKqEc!r+;dwiT!oF?;+Fb+8s*s{9;GBg!ns-Ll&4 zHNWgyH)7cTY?Ysz^=Kj~nuEF7;*D}kFbVO*r`5yXWr3L(X)?p0;RayGZowNWV3K6J z`X*F0M$&e)C}ra3SufO~t#FSFEKD`5kg%S$wfj2MRNtY5qfO7EYfJPTsJbLWT2f69 zE?txpt%Q;YRCctV;q!LW2t5>Ue?h|`Uu>Eoc~<8!HTy0&F=tD&hlQr0+!U`t#N*xB zP{+jkR#(t{5M^K-^SW}Ah~mpd;#xv_{&Su@I6X9 z2C<>y6^1}VNULIUTUw~vnxNNfAvP0bm|IRxPAAl?kOnA-nyM4=F$K;f*|T=UwiEck znb?eBA*`&donUEmL7G+4wj>T=Q<0F6EC4E`!#}T%xy8$?S@~cxANw0b|4b|_vWVS= zEf~}xNRypkF5XiK#|ar-h!Y!HgTG-%1hj)T~z;daFNJP*dV>BKQws#Dy8V3iN%Q8E*&=uhn6@<*Wn1 z#r%a11G>>S(qjvsUol_5u8JqCmnL;bG7|hu#7)4M8EJs6J-!K5LmjZ`Wbk;+m?dNY zO%u)s%A|odFn4GHVL{j6&??qlvQVKxxgxYZj*@DqI$`yPI#MGb_AXW^dg=WI9Wc;s7v5r@4r& zKeUV_*FPBNB@;HYNcwuSJ<@y~!NZr-`_W;Ld0Art!X<`ozX^2?k_iMr5r=vijH+wdh;3|>nz4WGu&tyi}Ykv^BLX#V}h2TF3 zUrP|DghHdJ8d|Ufl)ewG*f4VJae;+k3=841;7z}i+7y!+DOO0RYEa=^pNOPFA9TBj zF_SJ$g{M&S#B;hmP$D5+b3mPupV%jP)r04%$IR)=l&fs0C?YmKzOoO4c>a08?<*ar zO%S~s#Z8+?!+t!T@NLp7RNui`B_28qUm{8QMQx%xH0}k4rrI{#b?Ln#+Yum?Rbs~f zFsABfU03m0lx(20yrsG^QXuk0cS|Q;e?nSXai(BxT#+Mx6(IC1lB~;|D;*sD*LPl#yr!7jHs-gEYa-f1WJO)?LQj9w0Z|VIQ8=o{~ z!vIZ(1Oji}Piwe?lmeGGKaN9BHF%7npD1VyJn=0-PSjzzwBq8yOdJ+omNYv%yRvxk zE*+=)@2y3}Pm&90HzX)+8EUqV`hWrXo{reIw5wznLp2ec6C#;J74iW=WjR7x2HDCK zyL>@qb4E*GsUGu))H>_o-r(rl3R!QilIxitca94wsQCsaD5%|Czn;Zr?O8S$4s>Y$ z(MWL@XF7%W3(aEkBm?c@#T&ani>+wmQ-ZZ114Lz0{T=A+gP}XSXs~d9D%pX|= zCPrw+!HgN)M)1p@+3Xoc8VO)~tSRYnosNjdES%a&ZCjj*wg9Ak1to^d7ON}Up4;E@ zh;)+=m6eraK-bIG^c0j;f$DdHDkNhVJ`?uXlCh$kXs{QNVv=RCOaQ}MLTiCpo?u+r zD_qh}k0003{UK8bi>xOzZ+yRwuyBDNT3}DKp3k4GCG)I=m4Msn$^}3B05VIx$F@Dz zl^)*)$rIamD{AC<9^^EMMaMQu&%hP%qjAptwdcJCBN4OvLVg+$XCxPmEE**ZZYIJt zlecOT7d^^CR;6>4{P#6lVdE305WT|HaJ!f$BYE|iz`cio9|AB^Ex+X;?wz8$VeE5} z7?GwH5E(O_PXwg({IrUSiVk`M5?4J4en~IjJq-hRO+Kh5aYLEGoQQXMt_uT5z#HH? zT$|vctV`eyyKU4@ z_M#!h&)0mihTQI6(Y1dwD@2CmxOak{(4z+j>y7F>H1O2#y>h$1ep}9&;hB<$M${8| zdo~xWl4s7$@q#n`Bs1(XudhqG(@;tJ&wg(8FqmWbzC=_9?yS2mHi$JdOe{-o`)K3x0CB*KsQWJ({1< z&kZz{&q6{PrD}e6F(Jfu;n)(mFL$$>=0i2Fy~xs|K9JM3;o{*BQSzX;tN6mROu*!C zscz;f5VVTVw#nihNRnfD?@Aq8vF=UGc%V?#UHZ{C!; ze<*`;{g#2bIXwdOz+NFX>rcJ0U70@=zm+^9D4t*D;vSmi^i*+V*Uu6x8}hpFaulNM z$S2Q{@}P|fM@Wg2 zr?G$IyBJIXX_&n3xaQ}hZTrUif~9lDB$r2Z&~tBNwR^dfmi{rZ5E5djV1Q1qiFxOx z6c)eC7!V2r6+xUHQ?kST~8h|3?6cV1&XWk zJ!2{>YQF0-##a=B;-jBAcV9oQR$;K9TBdx8)Cu8Nz)KjKfrj-?-v@4i%;;vv$hlVo3o?3 z`9%4p{PbVr0{Y!ee;ydulMZ?=H5iu&DNtT%4PPk# zn=dA_+sNLzGLcdK|Fm}{?oh6Mm}$yPsYGQ-Wr`ss%9bTrkF`m~DOu8qro|T0h&t+2 zqQxE}OA(Sp6emT}Vu?bfET6I_B}B+M=er+D=Xrn6Z@KUL z$Gi3-Q$(x8KdDoxXBt4vnzzKG#|{mW{PBgsU@Dy4LixS>nYqf;-|Hxb&3*Dan&;a$ zR}2piPdoQ0QHkPv<+B|@o{qh4DB_DXtb_L5mO47{;Z-?EqZ@B{v1h*}Zue!wpj&NC zdAq;=?f56$nA*=veU4E_&3G=k17!s?)wPlorh4yi)T*7eivwBf7M!Rd2II#o>KFVX zr5y1nYJ<=--M#5sGFO5j67%N`UsPn;D@1pklI;=ay2Vj@;EF}fUbfmf>1p@xRhN&l zW7S6m&ye~tW{l%=FgCS+0!NfCvcAZY!T`wiN=5JZh97^YtoOU5_-uajD8sbqb-l4X z`$^OEhBZG&MOEevtV~dmS7jfdoFgdN3>UfUDEW^)Gvr-+HfCmRZJj@re+fXY(~h<( zOsp6#&J)%9Z#ma9V^m$wgZ^4}@9;NC zWo66i>grZVw}(bXN~6fR37sqgPM;1N8sdVTD#)3xLN{-!Q2*JCvfvgptC z{AZOS9C1XgEqhN5bHEB0gm_zh*XQ7-iKl67m8}WHH6lN6Yr2q;v2jSgM~8{Kd$wQ{ z!NnmWmF-DxnwHrK!72tc%r@$hu63(Hl1{U_d8LW*ntK|Nj3MjqnyR)@L1>Nd&<(RF z1)TuG**1pUUbtaR=d{_KKyJw?vMVj#DK1-PjPlp6FG}K*zl5J4T}4c0&Am#MP%(tW z=7W8?}pla zms2}B1g-CNn$Jgxs{#Wd)gvB3>CWoJcYeJc+zWjK6aM~SYpj9@tpW-#BVT(mi*5L3 ziHm%egVKWai2(u*5EK%ca&OVfiOoYUoPOKf#KR--!B}6%f~th=P#|nnWJ&?!!NO{f zFhn-NIv&iv?H%db_^0K_m-m;m8&I{bEC^V$deVw>bTEH2wibj_o7XoK{$9AkChwU% zTlKEgxtm~l4u*s1zf69`f=QpD0w2OokSiK?*5f%QWVJU^e!I)CGSUH7j=9O7a|+V( zw0}Nvv_lx2j_YqO&uA1{S|c(%*X-`TIrVmQ^)FD0!L%~@a|dQPla$_=^G)8H&+!;Y z_skTOImr_`ZT&@cOTr>HJz4vwwU(~4^)07m*ldpOjHb4qAJL}pVi83ZzQoqeqMFa2 zT6#>ywaFMKhytqf%-{GBWK~H-y%Zxk3zV(j0imky$}fN-Zb3cdAV?# zg%)^V!-&Sd%({Ps`$m!pF|ZFVp4}qdRHj)}f@UnqNPWD02n4d_L2Lx~y*j}{tvEV7=X z?HyiMx0U3ZGhW)({$_YagwauXai?fRLY9mw*YpiaU^ma!*dK93GB`AJ2@G!}wnd$+ zwe$W{!B3ID%8VfBQ{YUj8rn#1`G+mVg@`VuL*R_KWE71^%=Y#5kzH@+{DDZAsy1=- zUi9lfvc7D+Yu6PM8gjL>jng(Id(K>k=05ZpGr}Nc+0|7w2Cn_Nm~t?6t^vIx3DN?> z!YO;*4|$wd)X=a75mEMGI}&8!*aM=K??Jt^@F<#Mooq7Cwxz{MdhbC*@$dir+n6^{ z@*p&OvY*lq6MBC&5H6z7F$IHj6aB%dQ#9pAD55(6DnueyBcRNPxNKKJ>Yb{f#Sj8O z_AcE9ZZRE&JnzBwD zC}}emXiQyx6!`=~roVf=JYZYgD;tlq5>^`sPZ~X<##Conahl7Jsvs@I()tsL4#AgC zjn)c5=~WN_mPO-@u)enPa%co6JkohTKlZWy@Oj8n8b-LiugkiuEg^6Fr^Wo0Hk}b_ z5}QH!T zf3PoW*)_U{LdnaWLjAfwA9D%b#VAmW@C4cZak>a+QaGRBYumefN=r*gwp7^RxWOQ@ zyX9qoHz$@zh0b7<3W#ZjZ%$!Fz{2E@`r@>xhDWgcivK~u_yr90Rco^Qi)&t|Zd~em zt85>sp-sGs_Q9gqE#2vFFQve)6mQEwGWgyWYo`}iBWk}F$zI3s7w%AJ*gOaHH>2PZaug>6DoaZA3HROPP{}V`#z>p8 z3!qG;u$7N*%{xj?7UVV^J~!WAl(}HXja2VpfFaVT>$d3?C#0sz7@L@cqC1ja^Zk+o z0vC;BzCdRoZ`2o_OxHE(vRr<^bET$3G%2j_-la#(VovXW09M+(qiD$W_{T%_0-x@% zs}@D8d0e?|>pl5o<`62!a+`uRQS=5dW{;_#fAXnokKV4=(84e@4=l+dN}IA4UxDz1 z1o%40pl+ac7=%dB-sX{<-}J}>18Ra(66dG9gxHXBL;@`Xn~*U3)4TJqCRyqqyqk-7 zH$+;yEzIuMt<$Z|=Jv9J{i1QkJxwalayH;Opaz5g$?`C3gDMB5tJ1QvO>DJ=F?XtJ zY5?+JJiYV2j&Dc)XU<5qM5xBWPrfI%4mz3*aywWOyj$>f-rIwV94P|sMwHg{gqu^$ zXl+cXQzz6d2daa~`4wrQi?aS@lzKb^3JjJ0 zzCNd$yS599fu0}`AGw%vZ`R|#Cz68A?UK56^CE8s-M)32i zXclL4FW(YX^RsOADF$)hwCj04wZiVhvSoILT$`^cDlo9Phk$o@UEAdP5ja4|mKa4mu@ssA@C)tBNPL#BX zWpvbkQg5JZtZk@9`bhwYWhC;4*CxK{M}I9rAn- zA~E6cVr>Ih9LqVC^*H5jL&@H*9ynf6G==xH;G za0=%Ez+K<`wBpGVbE(a6vzbWN5%x#=TXnB)86@obp!ssuBMyc>6hNRM1J6rZYy<$J z+tOd%=$^Dm5#6vdL$dU42#IL<<|R~=qM$LeelLr4PVl_tRf`Z9dNGyjx2GU!ZB|@d zOj>j`d9@8p+CvG%v03TVJ-332R5|svZIHOy5^$Gj7c%0s4sUxwGg#w_6Y-B-+Z$+H zQ|6b8;D1}amW{0+jbBX3C_)wvk+0vc_M*B|p={B)#*8q$Z*W{R!;2^?r|3dD8GPDuuQO{J!buCpOjZ+J} zZ$3_5|K;hscsry+3zeMTScHa#jw&+PpODs zAGqx(jY52DdwrMtHWbpO5(@fP-(2(pJHaV8wUlVYgE*xmEci2S<%nx?^) zE@xdHV}FEXE8r1?gG@#*h!hCxsEUs)D9|L415Ac2V|wOp=*cC+$s(|ACSP{I*?co@ zAM+JiHg>SKAHB7sqr7c<2~Xzn8j`o2c=`3>PtLN-=5UW<{3uw7Ojv8iL|ig)VK0WY zsCnjY&wZ!yYn*aZxL1Ar`U+Y5nR0xSPX+P`++gj*_fA~={{~#g=f6(T?MpQ|qG}3U lYdOBC+NpD7-sZjihp(qdSJd&`vpYQaXKcXIzpiV4?7wC;53c|K diff --git a/src/fundamentals/multilocation/example.md b/src/fundamentals/multilocation/example.md index b3fcffc..449d09b 100644 --- a/src/fundamentals/multilocation/example.md +++ b/src/fundamentals/multilocation/example.md @@ -2,7 +2,37 @@ In this example we show different `MultiLocation`s for the system hierarchy in the image below. ![Example](./../images/MultiLocation_Example.png) +From the perspective of RelayA ```rust,noplayground -// Todo: Make MultiLocations +// ParaA +let _: MultiLocation = Parachain(1000).into(); +// AccountId32 in Parachain A +let _: MultiLocation = (Parachain(1000), AccountId32 { network: RELAY_A_NETWORK, id: [0u8; 32]}).into(); +// Asset in Parachain A +let _: MultiLocation = (Parachain(1000), PalletInstance(1), GeneralIndex(1)).into(); +// Ethereum based account on Parachain B +let _: MultiLocation = (Parachain(2000), AccountKey20 { network: RELAY_A_NETWORK, key: [0u8; 20] }).into(); +// Smart Contract +let _: MultiLocation = (Parachain(2000), PalletInstance(1), AccountKey20 { network: RELAY_A_NETWORK, key: [0u8; 20] }).into(); +// RelayB +let _: MultiLocation = (Parent, GlobalConsensus(RELAY_B_NETWORK)).into(); +// NFT on Parachain C +let _: MultiLocation = (Parent, GlobalConsensus(RELAY_B_NETWORK), Parachain(1000), GeneralIndex(1)).into(); +``` +From the perspective of Parachain C +```rust,noplayground +// Relay A +let _: MultiLocation = Parent.into(); +// Plurality Example. Many more BodyId/BodyPart combos imaginable +let _: MultiLocation = (Parent, Plurality { id: BodyId::Index(0), part: BodyPart::Members { count: 10 } }).into(); +// Account in Relay +let _: MultiLocation = (Parent, AccountId32 { network: None, id: [0u8; 32] }).into(); ``` + +From the perspective of the Smart Contract +```rust,noplayground +// Asset in Parachain A +let _: MultiLocation = (Parent, Parent, Parachain(1000), PalletInstance(1), GeneralIndex(1)).into(); + +``` \ No newline at end of file From d39d8d3f1eb89f6697425a6b7c6852363877faf7 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Tue, 9 May 2023 10:18:53 +0200 Subject: [PATCH 27/73] address feedback --- src/journey/expects.md | 21 +++++++++++++++------ src/journey/queries.md | 30 +++++++++++++++++++++++++----- src/journey/version.md | 9 +++++++-- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/journey/expects.md b/src/journey/expects.md index 31805f1..efa8929 100644 --- a/src/journey/expects.md +++ b/src/journey/expects.md @@ -1,5 +1,8 @@ # Expects -XCM contains instructions to check for specific conditions during the execution of the message. These 'expect' instructions check for a specific condition and if the condition is not as expected, the instruction throws an error. These instructions are, for example, useful to check the state of the registers before executing specific instructions. XCM contains the following expect instructions: +XCM contains instructions to check for specific conditions during the execution of the message. +These 'expect' instructions check for a specific condition and if it's not fulfilled, an error is then thrown. +These instructions are used for things like checking the state of the registers before executing specific instructions. +XCM contains the following expect instructions: - `ExpectAsset` - `ExpectOrigin` - `ExpectPallet` @@ -39,7 +42,8 @@ ExpectOrigin(Option) ``` ### Example -For the full example, check [here](TODO). +For the full example, check [here](TODO). +The `ExpectOrigin` instruction errors because the `ClearOrigin` clears the origin register and we expect it to be equal to `Parachain(1)`. ```rust,noplayground // Set the instructions that are executed when ExpectOrigin does not pass. // In this case, reporting back an error to the Parachain. @@ -54,10 +58,12 @@ ExpectOrigin(Some(Parachain(1).into())), ``` ## ExpectPallet -The `ExpectPallet` instruction ensures that a particular pallet with a particular version exists. +The `ExpectPallet` instruction ensures that a particular pallet with a particular version exists in the destination's runtime. It throws a `PalletNotFound` error if there is no pallet at the given index. It throws a `NameMismatch` error is the `name` or `module_name` mismatch and a `VersionIncompatible` error if the `crate_major` or `crate_minor` mismatch. The `name` and `module_name` represent a byte representation of the pallet's name and module name (e.g. 'Balances' and 'pallet_balances'). +Consensus systems that are not substrate-based may throw an `Unimplemented` error for this instruction. + ```rust,noplayground ExpectPallet { #[codec(compact)] @@ -94,7 +100,9 @@ ExpectPallet { ``` ## ExpectError -The `ExpectError` instruction throws an `ExpectationFalse` error if the error register does not equal the expected error. This instruction is useful during the error handler execution to halt the error handler if the error that started the execution of the error handler is not as expected. The `ExpectError` instruction allows to only execute the instructions in the error handler, when a specific error is thrown. +The `ExpectError` instruction throws an `ExpectationFalse` error if the error register does not equal the expected error at that point in the execution. +This instruction is useful during the error handler execution to halt the error handler if the error that started the execution of the error handler is not as expected. +The `ExpectError` instruction allows to only execute the instructions in the error handler, when a specific error is thrown. ```rust,noplayground ExpectError(Option<(u32, Error)>) ``` @@ -122,7 +130,8 @@ ExpectPallet { ## ExpectTransactStatus The `ExpectTransactStatus` instruction throws an `ExpectationFalse` error if the transact status register does not equal the expected transact status. -The status is described by the `MaybeErrorCode` enum, and can either be a Success, Error or TruncatedError if the length of the error exceeds the MaxDispatchErrorLen. For pallet-based calls, the Error is represented as the scale encoded `Error` enum of the called pallet. +The status is described by the `MaybeErrorCode` enum, and can either be a Success, Error or TruncatedError if the length of the error exceeds the MaxDispatchErrorLen. +For pallet-based calls, the Error is represented as the scale encoded `Error` enum of the called pallet. ```rust,noplayground ExpectTransactStatus(MaybeErrorCode) @@ -135,7 +144,7 @@ pub enum MaybeErrorCode { ### Example For the full example, check [here](TODO). -The transact status is reported to `parachain(1)` if the call in the `Transact` errors. +The transact status is reported to `Parachain(1)` if the call in the `Transact` errors. ```rust,noplayground SetErrorHandler(Xcm(vec![ReportTransactStatus(QueryResponseInfo { destination: Parachain(1).into(), diff --git a/src/journey/queries.md b/src/journey/queries.md index 39f6902..354e9fa 100644 --- a/src/journey/queries.md +++ b/src/journey/queries.md @@ -6,9 +6,8 @@ XCM contains query instructions that can be used to query information from anoth - `ReportTransactStatus` -Each of these instructions is send to the destination of which we would like to query the information. -Each instruction has as one of its inputs a `QueryResponseInfo` struct. -The `destination` tells the queried consensus system where to send the response to and the `query_id` field links the query and the query response together. The `max_weight` field tells the queried consensus system what the maximum weight is that the response instruction can take. +Each of these instructions is sent to the destination where we would like the information to be reported back to us. +Each instruction has a `QueryResponseInfo` struct as one of its inputs. ```rust, noplayground pub struct QueryResponseInfo { @@ -19,8 +18,11 @@ pub struct QueryResponseInfo { } ``` +The `destination` tells the queried consensus system where to send the response to and the `query_id` field links the query and the query response together. The `max_weight` field tells the queried consensus system what the maximum weight is that the response instruction can take. + When a query instruction is executed correctly, it sends a `QueryResponse` instruction to the location defined in the previously described `destination` field. -The `QueryResponse` looks the following: +The `QueryResponse` looks like this: + ```rust,noplayground QueryResponse { #[codec(compact)] @@ -55,12 +57,13 @@ The response can be send back to the requester, or to another location, so the q Now we take a look at the query instructions. ## ReportHolding -The `ReportHolding` instruction report to the given destination the contents of the Holding Register. The `assets` field is 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*. For example, if the holding register contains 10 of some fungible asset and the `assets` field specifies 15 of the same fungible asset, the result will return 10 of that asset. No wildcards will be used when reporting assets back. ```rust, noplayground ReportHolding { response_info: QueryResponseInfo, assets: MultiAssetFilter } ``` +The `ReportHolding` instruction reports to the given destination the contents of the Holding Register. The `assets` field is 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*. For example, if the holding register contains 10 units of some fungible asset and the `assets` field specifies 15 units of the same asset, the result will return 10 units of that asset. Wild cards can be used to describe which assets in the holding register to report, but the response always contains assets and no wild cards. + ### Example For the full example, check [here](TODO). Assets are withdrawn from the account of parachain 1 on the relay chain and partly deposited in the account of parachain 2. The remaining assets are reported back to parachain 1. ```rust, noplayground @@ -86,6 +89,23 @@ The `QueryPallet` instruction queries the existence of a particular pallet based QueryPallet { module_name: Vec, response_info: QueryResponseInfo } ``` +The destination responds with a vec of `PalletInfo`s if the pallet exists. + +```rust,noplayground +pub struct PalletInfo { + #[codec(compact)] + index: u32, + name: BoundedVec, + module_name: BoundedVec, + #[codec(compact)] + major: u32, + #[codec(compact)] + minor: u32, + #[codec(compact)] + patch: u32, +} +``` + ### Example For the full example, check [here](TODO). It queries for all instances of pallet_balances and sends the result back to parachain 1. diff --git a/src/journey/version.md b/src/journey/version.md index 70752d9..ff8e5e3 100644 --- a/src/journey/version.md +++ b/src/journey/version.md @@ -1,9 +1,14 @@ # Version Subscription -XCM is a versioned messaging format. One version may contain more or different instruction then another, so for parties to communicate via XCM it is important to know what version the other party is using. XCM enables a version subscription model, where parties can subscribe to each other for version information. XCM has two instructions to enable this: +XCM is a versioned messaging format. One version may contain more or different instructions than another, so for parties to communicate via XCM, it is important to know which version the other party is using. XCM enables a version subscription model, where parties can subscribe to each other to get notified of version updates. XCM has two instructions to enable this: - `SubscribeVersion` - `UnsubscribeVersion` -The version subscription model can differ per XCVM implementation. The `xcm-executor` has a `SubscriptionService` [config item](../executor_config/README.md). When the `SubscribeVersion` instruction is send to a consensus system that implements the `xcm-executor` with the `xcm-pallet` as implementation for the `SubscriptionService`, the system will send back its currently `AdvertisedVersion` and will keep the subscribed location up to date when the version changes. The subscribed location can stop the subscription by sending the `UnsubscribeVersion` instruction. +The version subscription model can differ per XCVM implementation. +The `xcm-executor` has a `SubscriptionService` [config item](../executor_config/README.md). +Any type specified as the `SubscriptionService` must implement the `VersionChangeNotifier` trait. +The XCM pallet is one such implementor. +When the `SubscribeVersion` instruction is sent to a consensus system that uses the XCM pallet as the `SubscriptionService` in the XCM executor, the system will send back its currently `AdvertisedVersion` and will keep the subscribed location up to date when the version changes. +The subscribed location can unsubscribe to version changes by sending the `UnsubscribeVersion` instruction. ```rust,noplayground SubscribeVersion { From bdc6ad73dca4501e20f95c8ab67740e473f4fcb6 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Wed, 10 May 2023 16:25:50 +0200 Subject: [PATCH 28/73] add transact --- src/SUMMARY.md | 2 +- src/journey/origins.md | 0 src/journey/transact.md | 129 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 src/journey/origins.md create mode 100644 src/journey/transact.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index d1c20c8..d3f1ff7 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -27,7 +27,7 @@ - [Locks]() - [Origin]() - [Channels]() - - [When all else fails]() + - [When all else fails](journey/transact.md) - [Misc]() - [Config Deep Dive](executor_config/README.md) - [Testing]() diff --git a/src/journey/origins.md b/src/journey/origins.md new file mode 100644 index 0000000..e69de29 diff --git a/src/journey/transact.md b/src/journey/transact.md new file mode 100644 index 0000000..f6e92e0 --- /dev/null +++ b/src/journey/transact.md @@ -0,0 +1,129 @@ +# Transact +XCM has an instruction that allows for the execution of calls (Frame-based systems have Runtime calls or extrinsics) in a consensus system. +It is the `Transact` instruction and it looks like this: + +```rust,noplayground +Transact { + origin_kind: OriginKind, + require_weight_at_most: Weight, + call: DoubleEncoded +} +``` + +The Transact instruction has three fields. +The `origin_kind` has as type [OriginKind](https://paritytech.github.io/polkadot/doc/xcm/v2/enum.OriginKind.html) and specifies how the origin of the call should be interpreted. +In the xcm-executor, the `origin_kind` is used to determine how to convert a `MultiLocation` origin into a `RuntimeOrigin`. +For more information, check out the [xcm-executor config docs](../executor_config/index.html). + +The `require_weight_at_most` field tells the XCVM executing the call how much [weight](../fundamentals/fees.md) it can use. +If the call uses more weight than the specified `require_weight_at_most`, the execution of the call fails. + +The `call` field has as a type `DoubleEncoded`. + +```rust,noplayground +pub struct DoubleEncoded { + encoded: Vec, + #[codec(skip)] + decoded: Option, +} +``` + +XCM is consensus system agnostic, so it does not know what is being encoded in the call field. So the field has to be opaque. +However, the XCVM knows how to interpret this call field and how to decode it. +Instead of just using a `Vec` we use `DoubleEncoded` as a wrapper around a pre-encoded call (`Vec`) with extra functionalities such as caching of the decoded value. +We like to emphasize that the call in the `Transact` instruction can be anything from a `RuntimeCall` in a Frame-based system, to a smart contract function call in an EVM-based system. + +[//]: # (Todo: Move Transact Status explanation from expect to here.) + +Each XCVM has a Transact Status Register, to record the execution result of the call that is dispatched by the `Transact` instruction. +*Important note:* The execution of the XCM instruction does *not* error when the dispatched call errors. + +## Xcm-Executor +In this section, we quickly look at how the Xcm-executor executes the `Transact` instruction. + +It executes, among other things, the following steps: +1. Decoded the call field into the actual call that we want to dispatch. +2. Checks with the [SafeCallFilter](../executor_config/index.html#safecallfilter) if the execution of this call is allowed. +3. Use the [OriginConverter](../executor_config/index.html#originconverter) to convert the `MultiLocation` origin into a `RuntimeOrigin`. +4. Check if the call's weight does not exceed `require_weight_at_most`. +5. Dispatch the call with the converted origin and set the `transact_status` register accordingly. +6. Calculate the weight that was actually used. + + +## Example 1 +For the full example, check [here](TODO). + +In this example, the relay chain executes the `set_balance` function of `pallet_balances` on `Parachain(1)`. +This function requires the origin to be root. We enable the root origin for the relay chain by setting `ParentAsSuperuser` for the `OriginConverter` config type. +```rust,noplayground +let call = parachain::RuntimeCall::Balances( + pallet_balances::Call::::set_balance { + who: ALICE, + new_free: 5 * AMOUNT, + new_reserved: 0, + }, +); + +let message = Xcm(vec![ + WithdrawAsset((Here, AMOUNT).into()), + BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + Transact { + origin_kind: OriginKind::Superuser, + require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + call: call.encode().into(), + }, +]); +``` + +## Example 2 +For the full example, check [here](TODO). + +In this example, as Parachain(1), we create an NFT collection on the relay chain and we then mint an NFT with ID 1. +The admin for the nft collection is parachain(1). The call looks as follows: +```rust,noplayground +let create_collection = relay_chain::RuntimeCall::Uniques( + pallet_uniques::Call::::create { + collection: 1u32, + admin: parachain_sovereign_account_id(1), + } +); +``` + +The owner of the NFT is Alice. The nft mint call looks as follows: +```rust,noplayground +let mint = relay_chain::RuntimeCall::Uniques( + pallet_uniques::Call::::mint { + collection: 1u32, + item: 1u32, + owner: ALICE, + } +); +``` + +The xcm message contains the following instructions: +1. Withdraw native assets from the `Parachain(1)`'s sovereign account. +2. Buy weight with these assets. +3. Create a collection with as admin and owner the sovereign account of `Parachain(1)`. +4. Mints an NFT in the collection with item ID 1 and as owner Alice. +```rust,noplayground +let message = Xcm(vec![ + WithdrawAsset((Here, AMOUNT).into()), + BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + call: create_collection.encode().into(), + }, + Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + call: mint.encode().into(), + }, +]); +``` + +## Next: +Check out the following instructions that interact with the Transact Status Register: +- [ClearTransactStatus](TODO) +- [ReportTransactStatus](TODO) +- [ExpectTransactStatus](TODO) \ No newline at end of file From b124d714cadbd09aa8b3419918067d98861f1f04 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Thu, 11 May 2023 11:41:26 +0200 Subject: [PATCH 29/73] add origin --- src/journey/origins.md | 60 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/journey/origins.md b/src/journey/origins.md index e69de29..6c902e7 100644 --- a/src/journey/origins.md +++ b/src/journey/origins.md @@ -0,0 +1,60 @@ +# Origins +An XCMV needs context while executing Xcm instructions. To uses the `XcmContext` struct to provide contextual information while executing Xcm Instructions. +It contains information such as the origin of the corresponding XCM, the hash of the message, and the topic of the XCM. + +```rust +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]>, +} +``` + +In the XCMV, the origin field of the XcmContext expresses the `MultiLocation` with whose authority the current programme is running. +The origin is important for enforcing restrictions and ensuring appropriate execution of the instructions. + +There are multiple instructions in Xcm that can alter the XcmContext origin field: + +- `ClearOrigin` +- `DescendOrigin` +- `UniversalOrigin` +- `AliasOrigin` + +## ClearOrigin +```rust,noplayground +ClearOrigin +``` + +The `ClearOrigin` instruction clears the origin of the current XCM. Specifically, it sets the origin field of the XCM context to None. This ensures that subsequent instructions in the XCM cannot use the authority of the origin to execute operations. + + +## DescendOrigin +```rust,noplayground +DescendOrigin(InteriorMultiLocation), +``` +The `DescendOrigin` instruction is used to change the XcmContext origin to an interior location or the current origin. + +This can be useful when executing instructions that need as context a specific location within the current origin. + +Note that the XcmContext origin can hold up to a maximum of 8 `Junction`s, so when we try to append an `InteriorMultiLocation` that result in more than 8 `Junction`s, a `LocationFull` error is thrown. + +## UniversalOrigin +```rust,noplayground +UniversalOrigin(Junction) +``` + +The UniversalOrigin XCM instruction sets the Origin Register to be a child of the Universal Location. The Junction parameter should generally be a `GlobalConsensus` variant since only these are children of the Universal Location. + +Safety Note: Should only be usable if the Origin is trusted to represent a child of the Universal location. In general, no Origin should be able to represent the Universal Location's 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, but it is necessary when bridging XCMs between `GlobalConsensus` systems. + +## AliasOrigin +```rust,noplayground +AliasOrigin(MultiLocation) +``` +The AliasOrigin instruction is similar to the UniversalOrigin instruction, but it is primarily used for account IDs. +When executed, it switches out the current origin for the given MultiLocation. +THe AliasOrigin instruction would allow to remove certain prefix patterns such as Parent/Parachain(X)/ for certain values of X (thereby allowing sibling chains to use the same account IDs) or Parachain(X)/ (allowing a Relay-chain to use the account IDs native to its child parachains) or just Parent/ (allowing parachains to use AccountIds of the Relay-chain). +The AliasOrigin currently does not yet have an implementation in the `xcm-executor`. From cf5afca07b2efeedecbb580538fdf6afc4bc8976 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Thu, 11 May 2023 13:07:45 +0200 Subject: [PATCH 30/73] address feedback --- src/journey/transact.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/journey/transact.md b/src/journey/transact.md index f6e92e0..5b0c4f1 100644 --- a/src/journey/transact.md +++ b/src/journey/transact.md @@ -1,5 +1,5 @@ # Transact -XCM has an instruction that allows for the execution of calls (Frame-based systems have Runtime calls or extrinsics) in a consensus system. +XCM has an instruction that allows for the execution of calls (from a `RuntimeCall` in a Frame-based system, to a smart contract function call in an EVM-based system) in a consensus system. It is the `Transact` instruction and it looks like this: ```rust,noplayground @@ -11,14 +11,14 @@ Transact { ``` The Transact instruction has three fields. -The `origin_kind` has as type [OriginKind](https://paritytech.github.io/polkadot/doc/xcm/v2/enum.OriginKind.html) and specifies how the origin of the call should be interpreted. +The `origin_kind` is of type [OriginKind](https://paritytech.github.io/polkadot/doc/xcm/v2/enum.OriginKind.html) and specifies how the origin of the call should be interpreted. In the xcm-executor, the `origin_kind` is used to determine how to convert a `MultiLocation` origin into a `RuntimeOrigin`. For more information, check out the [xcm-executor config docs](../executor_config/index.html). The `require_weight_at_most` field tells the XCVM executing the call how much [weight](../fundamentals/fees.md) it can use. If the call uses more weight than the specified `require_weight_at_most`, the execution of the call fails. -The `call` field has as a type `DoubleEncoded`. +The `call` field is of type `DoubleEncoded`. ```rust,noplayground pub struct DoubleEncoded { @@ -28,7 +28,8 @@ pub struct DoubleEncoded { } ``` -XCM is consensus system agnostic, so it does not know what is being encoded in the call field. So the field has to be opaque. +XCM is consensus system agnostic; it does not know what is being encoded in the call field. +Hence, the field is a byte vector that can be freely interpreted in whatever form possible. However, the XCVM knows how to interpret this call field and how to decode it. Instead of just using a `Vec` we use `DoubleEncoded` as a wrapper around a pre-encoded call (`Vec`) with extra functionalities such as caching of the decoded value. We like to emphasize that the call in the `Transact` instruction can be anything from a `RuntimeCall` in a Frame-based system, to a smart contract function call in an EVM-based system. From 5772c01747a3164ddf2e8a1fcd765bf8e9ee06f6 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Thu, 11 May 2023 13:13:58 +0200 Subject: [PATCH 31/73] Update src/journey/queries.md Co-authored-by: Keith Yeung --- src/journey/queries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/journey/queries.md b/src/journey/queries.md index 354e9fa..723b635 100644 --- a/src/journey/queries.md +++ b/src/journey/queries.md @@ -52,7 +52,7 @@ pub enum Response { The `QueryResponse` has the same `query_id` as the request to link the request and response and takes over the `max_weight` from the `QueryResponseInfo`. It has the requested information in the `response` field. And it has the location of the querier relative to the queried location in the querier field. -The response can be send back to the requester, or to another location, so the querier field is important to determine where the request originated from. +The response can be sent back to the requester, or to another location, so the querier field is important to determine where the requested information is needed. Now we take a look at the query instructions. From 0eadfe514743c3f0b9b14a8e73d794565869d138 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Mon, 15 May 2023 11:44:32 +0200 Subject: [PATCH 32/73] locks docs --- src/SUMMARY.md | 2 +- src/journey/locks/images/Example1.png | Bin 0 -> 24025 bytes src/journey/locks/images/Example2.png | Bin 0 -> 38359 bytes src/journey/locks/locks.md | 189 ++++++++++++++++++++++++++ 4 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 src/journey/locks/images/Example1.png create mode 100644 src/journey/locks/images/Example2.png create mode 100644 src/journey/locks/locks.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index d1c20c8..1526989 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -24,7 +24,7 @@ - [Fees]() - [Expectations]() - [Queries]() - - [Locks]() + - [Locks](journey/locks/locks.md) - [Origin]() - [Channels]() - [When all else fails]() diff --git a/src/journey/locks/images/Example1.png b/src/journey/locks/images/Example1.png new file mode 100644 index 0000000000000000000000000000000000000000..0f17891069ec5fcfe68c8af2d6353f8bcf4363d5 GIT binary patch literal 24025 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 T(*_q7_@|+wt6Yn;d-%TqK%uQV literal 0 HcmV?d00001 diff --git a/src/journey/locks/locks.md b/src/journey/locks/locks.md new file mode 100644 index 0000000..de6595f --- /dev/null +++ b/src/journey/locks/locks.md @@ -0,0 +1,189 @@ +# Locking +Xcm enables the locking of assets, meaning the restriction of the transfer or withdrawal of assets on a chain. +The XCM locking mechanism consists of four instructions: `LockAsset`, `UnlockAsset`, `NoteUnlockable`, and `RequestUnlock`. +Let's explore each instruction in detail: + +## LockAsset +```rust,noplayground +LockAsset { asset: MultiAsset, unlocker: MultiLocation } +``` +The LockAsset instruction is used to lock locally held assets and prevent further transfers or withdrawals. +This instruction requires two parameters: + +- `asset`: The asset(s) to be locked. +- `unlocker`: The MultiLocation that can unlock the asset(s). This value must match the origin of a corresponding `UnlockAsset` instruction to unlock the asset. + +When the locking operation succeeds, a `NoteUnlockable` instruction is sent to the unlocker. +This instruction serves as a notification that the asset is now unlockable. + +## UnlockAsset +```rust,noplayground +UnlockAsset { asset: MultiAsset, target: MultiLocation } +``` +The `UnlockAsset` instruction removes the lock on a specific asset on the local chain, allowing it to be transferred if there are no other restrictions. +The following parameters are required: + +- `asset`: The asset to be unlocked. +- `target`: The owner of the asset on the local chain. + + +## NoteUnlockable +```rust,noplayground +NoteUnlockable { asset: MultiAsset, owner: MultiLocation } +``` +The `NoteUnlockable` instruction indicates that an asset has been locked on the system which the message originated from. +The locked assets can only be unlocked by receiving an `UnlockAsset` instruction from this chain. +This instruction requires the following parameters: + +- `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. + +It is essential to trust the origin to have locked the corresponding asset before sending this message. + + +## RequestUnlock +```rust, noplayground +RequestUnlock { asset: MultiAsset, locker: MultiLocation } +``` +The `RequestUnlock` instruction is used to send an `UnlockAsset` instruction to the `locker` for a given asset. +The following parameters are required: + +- `asset`: The asset(s) to be unlocked. +- `locker`: The location from which a previous NoteUnlockable was sent, and where the UnlockAsset instruction should be sent. + + +## Example +To get a better grasp on how these instructions work together, we give two examples in this section. +The examples use the xcm-executor with the pallet-xcm as the implementation for the `AssetLocker` xcm-executor config item. +An important note of this implementation is that only one lock with ID `py/xcmlk` is set per account. +The pallet-xcm implementation keeps track of all the xcm-related locks that are placed on an account and sets the most restricting one with the `py/xcmlk` lock ID. +This principle becomes more clear in the second example. + + +### Example 1 +Check out the full [example code](TODO). +The scenario of this example is as follows: + +Parachain A locks 5 Cents of relay chain native assets of its Sovereign account on the relay chain and assigns Parachain B as unlocker. +Parachain A then asks Parachain B to unlock the funds partly. +Parachain B responds by sending an UnlockAssets instruction to the relay chain. + +![Example](./images/Example1.png) + +1. send `LockAsset` instruction from ParaA to relay. +```rust,noplayground +ParaA::execute_with(|| { + let message = Xcm(vec![LockAsset { + asset: (Here, AMOUNT * 5).into(), + unlocker: (Parachain(2)).into(), + }]); + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); +}); +``` + +2. Parachain B receives this NoteUnlockable instruction from the relay chain. +```rust,noplayground +NoteUnlockable { + owner: (Parent, Parachain(1)).into(), + asset: (Parent, AMOUNT * 5).into() +} +``` + +3. Parachain A sends RequestUnlock instruction to Parachain B +```rust,noplayground +ParaA::execute_with(|| { + let message = Xcm(vec![RequestUnlock { + asset: (Parent, 3 * AMOUNT).into(), + locker: Parent.into(), + }]); + assert_ok!(ParachainPalletXcm::send_xcm(Here, (Parent, Parachain(2)), message.clone())); +}); +``` + +4. Parachain B sends an UnlockAsset instruction to the relay chain. We check if the lock is updated accordingly: +```rust,noplayground +assert_eq!( + relay_chain::Balances::locks(¶chain_sovereign_account_id(1)), + vec![BalanceLock { id: *b"py/xcmlk", amount: 2 * AMOUNT, reasons: Reasons::All }] +); +``` + + +### Example 2 + +Check out the full [example code](TODO). +The scenario of this example is as follows: + +Parachain A sets two locks on the relay chain with as unlockers Parachain B and Parachain C. +Parachain A then requests Parachain B to partly unlock. + +Note: The locks overlap. + +![Example](./images/Example2.png) + +1. Set locks on the relay chain. Unlockers: B, C; Locks registered in pallet-xcm: 10, 5. Lock set in pallet-balances: 10. + +```rust, noplayground +ParaA::execute_with(|| { + let message = Xcm(vec![ + LockAsset { asset: (Here, AMOUNT * 10).into(), unlocker: (Parachain(2)).into() }, + LockAsset { asset: (Here, AMOUNT * 5).into(), unlocker: (Parachain(3)).into() }, + ]); + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); +}); + +Relay::execute_with(|| { + assert_eq!( + relay_chain::Balances::locks(¶chain_sovereign_account_id(1)), + vec![BalanceLock { id: *b"py/xcmlk", amount: AMOUNT * 10, reasons: Reasons::All }] + ); +}); +``` + +2. Parachain B and C receive the `NoteUnlockable` instruction. +```rust, noplayground +ParaB::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![NoteUnlockable { + owner: (Parent, Parachain(1)).into(), + asset: (Parent, AMOUNT * 10).into() + }])] + ); +}); + +ParaC::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![NoteUnlockable { + owner: (Parent, Parachain(1)).into(), + asset: (Parent, AMOUNT * 5).into() + }])] + ); +}); +``` + +3. Parachain A sends a `RequestUnlock` instruction to Parachain B for 8 CENTS. +```rust, noplayground +ParaA::execute_with(|| { + let message = Xcm(vec![RequestUnlock { + asset: (Parent, 8 * AMOUNT).into(), + locker: Parent.into(), + }]); + + assert_ok!(ParachainPalletXcm::send_xcm(Here, (Parent, Parachain(2)), message.clone())); +}); +``` + +4. Parachain B Unlocks a part of the funds by sending a UnlockAsset to the relay chain. Check the lock in the balances-pallet. +Unlockers: B, C; Funds registered in pallet-xcm: 2, 5. +Lock set in pallet-balances: 5. + +```rust,noplayground +Relay::execute_with(|| { + assert_eq!( + relay_chain::Balances::locks(¶chain_sovereign_account_id(1)), + vec![BalanceLock { id: *b"py/xcmlk", amount: 5 * AMOUNT, reasons: Reasons::All }] + ); +}); +``` \ No newline at end of file From 9f03179ad772c3a97a4712f7b4ea989244c844c2 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Mon, 15 May 2023 14:45:50 +0200 Subject: [PATCH 33/73] address feedback --- src/journey/origins.md | 20 +++++++++++--------- src/journey/transact.md | 20 ++++++++++---------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/journey/origins.md b/src/journey/origins.md index 6c902e7..9723947 100644 --- a/src/journey/origins.md +++ b/src/journey/origins.md @@ -1,6 +1,7 @@ # Origins -An XCMV needs context while executing Xcm instructions. To uses the `XcmContext` struct to provide contextual information while executing Xcm Instructions. -It contains information such as the origin of the corresponding XCM, the hash of the message, and the topic of the XCM. +An XCVM contains contextual information while executing XCM instructions. +It uses the `XcmContext` struct to provide them. +`XcmContext` contains information such as the origin of the corresponding XCM, the hash of the message, and the topic of the XCM. ```rust pub struct XcmContext { @@ -13,10 +14,10 @@ pub struct XcmContext { } ``` -In the XCMV, the origin field of the XcmContext expresses the `MultiLocation` with whose authority the current programme is running. +In the XCVM, the origin field of the XcmContext indicates which `MultiLocation`'s privilege level that the current programme is using to execute. The origin is important for enforcing restrictions and ensuring appropriate execution of the instructions. -There are multiple instructions in Xcm that can alter the XcmContext origin field: +There are multiple instructions in XCM that can alter the XcmContext origin field: - `ClearOrigin` - `DescendOrigin` @@ -28,18 +29,20 @@ There are multiple instructions in Xcm that can alter the XcmContext origin fiel ClearOrigin ``` -The `ClearOrigin` instruction clears the origin of the current XCM. Specifically, it sets the origin field of the XCM context to None. This ensures that subsequent instructions in the XCM cannot use the authority of the origin to execute operations. +The `ClearOrigin` instruction clears the origin register in the XCVM. +Specifically, it sets the origin field of the XCM context to None. +This ensures that subsequent instructions in the XCM cannot use the privilege level of the cleared origin to execute operations. ## DescendOrigin ```rust,noplayground DescendOrigin(InteriorMultiLocation), ``` -The `DescendOrigin` instruction is used to change the XcmContext origin to an interior location or the current origin. +The `DescendOrigin` instruction is used to change the XcmContext origin to an interior location of the current origin. -This can be useful when executing instructions that need as context a specific location within the current origin. +This can be useful when executing instructions that require a specific location within the current origin. -Note that the XcmContext origin can hold up to a maximum of 8 `Junction`s, so when we try to append an `InteriorMultiLocation` that result in more than 8 `Junction`s, a `LocationFull` error is thrown. +Note that the XcmContext origin is a `MultiLocation` containing an `InteriorMultiLocation` enum; it can only hold up to a maximum of 8 `Junction`s, so when we try to execute multiple `DescendOrigin` instructions which would result in an `InteriorMultiLocation` containing more than 8 `Junction`s, a `LocationFull` error is thrown. ## UniversalOrigin ```rust,noplayground @@ -57,4 +60,3 @@ AliasOrigin(MultiLocation) The AliasOrigin instruction is similar to the UniversalOrigin instruction, but it is primarily used for account IDs. When executed, it switches out the current origin for the given MultiLocation. THe AliasOrigin instruction would allow to remove certain prefix patterns such as Parent/Parachain(X)/ for certain values of X (thereby allowing sibling chains to use the same account IDs) or Parachain(X)/ (allowing a Relay-chain to use the account IDs native to its child parachains) or just Parent/ (allowing parachains to use AccountIds of the Relay-chain). -The AliasOrigin currently does not yet have an implementation in the `xcm-executor`. diff --git a/src/journey/transact.md b/src/journey/transact.md index 5b0c4f1..80bb265 100644 --- a/src/journey/transact.md +++ b/src/journey/transact.md @@ -1,5 +1,5 @@ # Transact -XCM has an instruction that allows for the execution of calls (from a `RuntimeCall` in a Frame-based system, to a smart contract function call in an EVM-based system) in a consensus system. +XCM contains an instruction that allows for the execution of calls (from a `RuntimeCall` in a FRAME-based system, to a smart contract function call in an EVM-based system) in a consensus system. It is the `Transact` instruction and it looks like this: ```rust,noplayground @@ -30,25 +30,25 @@ pub struct DoubleEncoded { XCM is consensus system agnostic; it does not know what is being encoded in the call field. Hence, the field is a byte vector that can be freely interpreted in whatever form possible. -However, the XCVM knows how to interpret this call field and how to decode it. +However, the XCVM does not inherently know how to interpret this call field nor how to decode it; it is reliant on the `T` type parameter to specify the proper codec for the byte vector. Instead of just using a `Vec` we use `DoubleEncoded` as a wrapper around a pre-encoded call (`Vec`) with extra functionalities such as caching of the decoded value. -We like to emphasize that the call in the `Transact` instruction can be anything from a `RuntimeCall` in a Frame-based system, to a smart contract function call in an EVM-based system. +We like to emphasize that the call in the `Transact` instruction can be anything from a `RuntimeCall` in a FRAME-based system, to a smart contract function call in an EVM-based system. [//]: # (Todo: Move Transact Status explanation from expect to here.) Each XCVM has a Transact Status Register, to record the execution result of the call that is dispatched by the `Transact` instruction. *Important note:* The execution of the XCM instruction does *not* error when the dispatched call errors. -## Xcm-Executor -In this section, we quickly look at how the Xcm-executor executes the `Transact` instruction. +## XCM Executor +In this section, we quickly look at how the XCM executor executes the `Transact` instruction. It executes, among other things, the following steps: -1. Decoded the call field into the actual call that we want to dispatch. -2. Checks with the [SafeCallFilter](../executor_config/index.html#safecallfilter) if the execution of this call is allowed. +1. Decode the call field into the actual call that we want to dispatch. +2. Check with the [SafeCallFilter](../executor_config/index.html#safecallfilter) on whether the execution of this call is allowed. 3. Use the [OriginConverter](../executor_config/index.html#originconverter) to convert the `MultiLocation` origin into a `RuntimeOrigin`. -4. Check if the call's weight does not exceed `require_weight_at_most`. -5. Dispatch the call with the converted origin and set the `transact_status` register accordingly. -6. Calculate the weight that was actually used. +4. Check whether the call weight does not exceed `require_weight_at_most`. +5. Dispatch the call with the converted origin and set the `transact_status` register to be the result of the dispatch. +6. Calculate the weight that was actually used during the dispatch. ## Example 1 From b3ad4c037601032f6d480f98ada73cb2c1cbee35 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Mon, 15 May 2023 16:50:42 +0200 Subject: [PATCH 34/73] address feedback --- src/journey/locks/locks.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/journey/locks/locks.md b/src/journey/locks/locks.md index de6595f..8ac74b2 100644 --- a/src/journey/locks/locks.md +++ b/src/journey/locks/locks.md @@ -1,5 +1,5 @@ # Locking -Xcm enables the locking of assets, meaning the restriction of the transfer or withdrawal of assets on a chain. +Assets can be locked via XCM, meaning, the transfer or withdrawal of assets can be restricted via messages. The XCM locking mechanism consists of four instructions: `LockAsset`, `UnlockAsset`, `NoteUnlockable`, and `RequestUnlock`. Let's explore each instruction in detail: @@ -49,12 +49,12 @@ The `RequestUnlock` instruction is used to send an `UnlockAsset` instruction to The following parameters are required: - `asset`: The asset(s) to be unlocked. -- `locker`: The location from which a previous NoteUnlockable was sent, and where the UnlockAsset instruction should be sent. +- `locker`: The location from which a previous `NoteUnlockable` was sent, and where the `UnlockAsset` instruction should be sent. ## Example To get a better grasp on how these instructions work together, we give two examples in this section. -The examples use the xcm-executor with the pallet-xcm as the implementation for the `AssetLocker` xcm-executor config item. +The examples use the xcm-executor with the pallet-xcm as the implementation for the `AssetLocker` config item. An important note of this implementation is that only one lock with ID `py/xcmlk` is set per account. The pallet-xcm implementation keeps track of all the xcm-related locks that are placed on an account and sets the most restricting one with the `py/xcmlk` lock ID. This principle becomes more clear in the second example. @@ -74,18 +74,18 @@ Parachain B responds by sending an UnlockAssets instruction to the relay chain. ```rust,noplayground ParaA::execute_with(|| { let message = Xcm(vec![LockAsset { - asset: (Here, AMOUNT * 5).into(), + asset: (Here, CENTS * 5).into(), unlocker: (Parachain(2)).into(), }]); assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); }); ``` -2. Parachain B receives this NoteUnlockable instruction from the relay chain. +2. Parachain B receives this `NoteUnlockable` instruction from the relay chain. ```rust,noplayground NoteUnlockable { owner: (Parent, Parachain(1)).into(), - asset: (Parent, AMOUNT * 5).into() + asset: (Parent, CENTS * 5).into() } ``` @@ -93,7 +93,7 @@ NoteUnlockable { ```rust,noplayground ParaA::execute_with(|| { let message = Xcm(vec![RequestUnlock { - asset: (Parent, 3 * AMOUNT).into(), + asset: (Parent, 3 * CENTS).into(), locker: Parent.into(), }]); assert_ok!(ParachainPalletXcm::send_xcm(Here, (Parent, Parachain(2)), message.clone())); @@ -104,7 +104,7 @@ ParaA::execute_with(|| { ```rust,noplayground assert_eq!( relay_chain::Balances::locks(¶chain_sovereign_account_id(1)), - vec![BalanceLock { id: *b"py/xcmlk", amount: 2 * AMOUNT, reasons: Reasons::All }] + vec![BalanceLock { id: *b"py/xcmlk", amount: 2 * CENTS, reasons: Reasons::All }] ); ``` @@ -126,8 +126,8 @@ Note: The locks overlap. ```rust, noplayground ParaA::execute_with(|| { let message = Xcm(vec![ - LockAsset { asset: (Here, AMOUNT * 10).into(), unlocker: (Parachain(2)).into() }, - LockAsset { asset: (Here, AMOUNT * 5).into(), unlocker: (Parachain(3)).into() }, + LockAsset { asset: (Here, CENTS * 10).into(), unlocker: (Parachain(2)).into() }, + LockAsset { asset: (Here, CENTS * 5).into(), unlocker: (Parachain(3)).into() }, ]); assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); }); @@ -135,7 +135,7 @@ ParaA::execute_with(|| { Relay::execute_with(|| { assert_eq!( relay_chain::Balances::locks(¶chain_sovereign_account_id(1)), - vec![BalanceLock { id: *b"py/xcmlk", amount: AMOUNT * 10, reasons: Reasons::All }] + vec![BalanceLock { id: *b"py/xcmlk", amount: CENTS * 10, reasons: Reasons::All }] ); }); ``` @@ -147,7 +147,7 @@ ParaB::execute_with(|| { parachain::MsgQueue::received_dmp(), vec![Xcm(vec![NoteUnlockable { owner: (Parent, Parachain(1)).into(), - asset: (Parent, AMOUNT * 10).into() + asset: (Parent, CENTS * 10).into() }])] ); }); @@ -157,7 +157,7 @@ ParaC::execute_with(|| { parachain::MsgQueue::received_dmp(), vec![Xcm(vec![NoteUnlockable { owner: (Parent, Parachain(1)).into(), - asset: (Parent, AMOUNT * 5).into() + asset: (Parent, CENTS * 5).into() }])] ); }); @@ -167,7 +167,7 @@ ParaC::execute_with(|| { ```rust, noplayground ParaA::execute_with(|| { let message = Xcm(vec![RequestUnlock { - asset: (Parent, 8 * AMOUNT).into(), + asset: (Parent, 8 * CENTS).into(), locker: Parent.into(), }]); @@ -175,7 +175,7 @@ ParaA::execute_with(|| { }); ``` -4. Parachain B Unlocks a part of the funds by sending a UnlockAsset to the relay chain. Check the lock in the balances-pallet. +4. Parachain B Unlocks a part of the funds by sending an `UnlockAsset` to the relay chain. we check the lock in the balances-pallet. Unlockers: B, C; Funds registered in pallet-xcm: 2, 5. Lock set in pallet-balances: 5. @@ -183,7 +183,7 @@ Lock set in pallet-balances: 5. Relay::execute_with(|| { assert_eq!( relay_chain::Balances::locks(¶chain_sovereign_account_id(1)), - vec![BalanceLock { id: *b"py/xcmlk", amount: 5 * AMOUNT, reasons: Reasons::All }] + vec![BalanceLock { id: *b"py/xcmlk", amount: 5 * CENTS, reasons: Reasons::All }] ); }); ``` \ No newline at end of file From 99bf1f59ec87e234171dcede2fa862a05b258d02 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Mon, 15 May 2023 17:14:50 +0200 Subject: [PATCH 35/73] address feedback round 2 --- src/journey/locks/locks.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/journey/locks/locks.md b/src/journey/locks/locks.md index 8ac74b2..1380489 100644 --- a/src/journey/locks/locks.md +++ b/src/journey/locks/locks.md @@ -89,7 +89,7 @@ NoteUnlockable { } ``` -3. Parachain A sends RequestUnlock instruction to Parachain B +3. Parachain A sends `RequestUnlock` instruction to Parachain B ```rust,noplayground ParaA::execute_with(|| { let message = Xcm(vec![RequestUnlock { @@ -100,7 +100,7 @@ ParaA::execute_with(|| { }); ``` -4. Parachain B sends an UnlockAsset instruction to the relay chain. We check if the lock is updated accordingly: +4. Parachain B sends an `UnlockAsset` instruction to the relay chain. We check if the lock is updated accordingly: ```rust,noplayground assert_eq!( relay_chain::Balances::locks(¶chain_sovereign_account_id(1)), @@ -117,7 +117,7 @@ The scenario of this example is as follows: Parachain A sets two locks on the relay chain with as unlockers Parachain B and Parachain C. Parachain A then requests Parachain B to partly unlock. -Note: The locks overlap. +Note: The locks overlap. When there are two or more locks, the total assets that are locked is equal to the most restrictive lock (the lock that locks the most assets). When the most restrictive lock is unlocked, the total locked assets is than equal to the second most restrictive lock. ![Example](./images/Example2.png) @@ -126,8 +126,8 @@ Note: The locks overlap. ```rust, noplayground ParaA::execute_with(|| { let message = Xcm(vec![ - LockAsset { asset: (Here, CENTS * 10).into(), unlocker: (Parachain(2)).into() }, - LockAsset { asset: (Here, CENTS * 5).into(), unlocker: (Parachain(3)).into() }, + LockAsset { asset: (Here, 10 * CENTS).into(), unlocker: (Parachain(2)).into() }, + LockAsset { asset: (Here, 5 * CENTS).into(), unlocker: (Parachain(3)).into() }, ]); assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); }); @@ -135,7 +135,7 @@ ParaA::execute_with(|| { Relay::execute_with(|| { assert_eq!( relay_chain::Balances::locks(¶chain_sovereign_account_id(1)), - vec![BalanceLock { id: *b"py/xcmlk", amount: CENTS * 10, reasons: Reasons::All }] + vec![BalanceLock { id: *b"py/xcmlk", amount: 10 * CENTS, reasons: Reasons::All }] ); }); ``` @@ -147,7 +147,7 @@ ParaB::execute_with(|| { parachain::MsgQueue::received_dmp(), vec![Xcm(vec![NoteUnlockable { owner: (Parent, Parachain(1)).into(), - asset: (Parent, CENTS * 10).into() + asset: (Parent, 10 * CENTS).into() }])] ); }); @@ -157,7 +157,7 @@ ParaC::execute_with(|| { parachain::MsgQueue::received_dmp(), vec![Xcm(vec![NoteUnlockable { owner: (Parent, Parachain(1)).into(), - asset: (Parent, CENTS * 5).into() + asset: (Parent, 5 * CENTS).into() }])] ); }); From 29450850aacabec8161e8406bc5d236f82f6558d Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Tue, 16 May 2023 10:39:05 +0200 Subject: [PATCH 36/73] add more register modifier instructions --- src/SUMMARY.md | 1 + src/journey/register-modifiers.md | 45 +++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/journey/register-modifiers.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index d1c20c8..cecd00c 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -28,6 +28,7 @@ - [Origin]() - [Channels]() - [When all else fails]() + - [More register modifiers]() - [Misc]() - [Config Deep Dive](executor_config/README.md) - [Testing]() diff --git a/src/journey/register-modifiers.md b/src/journey/register-modifiers.md new file mode 100644 index 0000000..4c2a0d9 --- /dev/null +++ b/src/journey/register-modifiers.md @@ -0,0 +1,45 @@ +# Register Modifiers +In the previous chapters we already saw instructions that modified the XCVM registers. This chapter contains more instructions that change the XCVM registers. We will discuss the following instructions: +- `SetErrorHandler` +- `SetAppendixHandler` +- `ClearError` +- `ClearTransactStatus` +- `SetTopic` +- `ClearTopic` + +## SetErrorHandler +```rust +SetErrorHandler(Xcm) +``` +The `SetErrorHandler` instructions is used to set the Error Handler Register. As discussed in the [XCVM chapter](TODO), the Error Handler is executed when an error is thrown during the regular instruction execution. + +## SetAppendixHandler +```rust +SetAppendixHandler(Xcm) +``` +The `SetAppendixHandler` instruction is used to set the Appendix Handler Register. As discussed in the [XCVM chapter](TODO), the Appendix Handler instructions are executed after the regular and error handler instruction are executed. These instructions are executed regardless of whether an error occurred. + +## ClearError +```rust +ClearError +``` +The `ClearError` instruction clears the Error Register. More specifically, it sets the Error Register to None. + +## ClearTransactStatus +```rust +ClearTransactStatus +``` +The `ClearTransactStatus` instruction sets the Transact Status Register to its default, cleared, value. + +## SetTopic +```rust +SetTopic([u8; 32]) +``` +The `SetTopic` instruction sets the Topic Register. + +## ClearTopic +```rust +ClearTopic +``` +The `ClearTopic` instruction clears the Topic Register. + From 2404eea71f9b8bbf55e5bdc832f1ca57f5fe43b6 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Wed, 17 May 2023 16:45:22 +0200 Subject: [PATCH 37/73] holding modifiers and trap/claim --- src/SUMMARY.md | 1 + src/journey/holding-modifiers.md | 40 ++++++++++++++++++ src/journey/trap-and-claim.md | 69 ++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 src/journey/holding-modifiers.md create mode 100644 src/journey/trap-and-claim.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index d1c20c8..e899fde 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -28,6 +28,7 @@ - [Origin]() - [Channels]() - [When all else fails]() + - [More Holding Modifiers](./journey/holding-modifiers.md) - [Misc]() - [Config Deep Dive](executor_config/README.md) - [Testing]() diff --git a/src/journey/holding-modifiers.md b/src/journey/holding-modifiers.md new file mode 100644 index 0000000..e60e1d3 --- /dev/null +++ b/src/journey/holding-modifiers.md @@ -0,0 +1,40 @@ +# Holding Register Modifiers +Most of the XCM instructions alter the Holding Register. We already have seen instructions that alter the Holding Register, like the `WithdrawAsset` or `DepositAsset` instructions. In this chapter we go over more instructions that alter the holding register, namely: + +- BurnAsset +- ExchangeAsset + +## BurnAsset +```rust,noplayground +BurnAsset(MultiAssets) +``` +The `BurnAsset` instruction allows for the reduction of assets in the Holding Register by up to the specified assets. The execution of the instruction does not throw an error if the Holding Register does not contain the assets (to make this an error, use `ExpectAsset` prior). + +### Example +For the full example, check [here](TODO). +The Scenario of the example is as follows: +Parachain A withdraws 10 units from its sovereign account on the relay chain and burns 4 of them. +The relay chain then reports back the status of the Holding Register to Parachain A. We expect the Holding Register to hold 6 units. +Note: If we would have added more then 10 units worth of assets in the `BurnAsset` instruction, we would have burned all assets in the Holding Register and the execution would succeed. +```rust,noplayground +let message = Xcm(vec![ + WithdrawAsset((Here, 10 * CENTS).into()), + BuyExecution { fees: (Here, CENTS).into(), weight_limit: WeightLimit::Unlimited }, + BurnAsset((Here, 4 * CENTS).into()), + ReportHolding { + response_info: QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_parts(1_000_000_000, 64*64) }, + assets: All.into() + } +]); +``` + +We expect the following response: +```rust,noplayground +Response::Assets((Parent, 6 * CENTS).into()) +``` + + +## ExchangeAsset diff --git a/src/journey/trap-and-claim.md b/src/journey/trap-and-claim.md new file mode 100644 index 0000000..4d018ba --- /dev/null +++ b/src/journey/trap-and-claim.md @@ -0,0 +1,69 @@ +# Trapping and Claiming assets. +When we reach the end of the execution of the XCM there can still be assets in the Holding Register. We can do nothing with them (essentially burning the assets) or we can trap the assets. When we trap the assets, we keep track of the assets together with the origin of the XCM. The origin can claim the assets back in one of the next XCMs. We have two instructions related to trapping and claiming assets: + +- `Trap` +- `ClaimAsset` + +## Trap +```rust,noplayground +Trap(#[codec(compact)] u64) +``` +The `Trap` instruction throws an error of type `Trap`. Both the Trap instruction and Trap error take an `u64` that can be used to represent some value. The Trap instruction is useful for throwing custom errors. An important thing to note is that the Trap instruction does not directly trap assets. It can however forcefully halt the further execution of instructions and if there are still assets in the Holding Register, these assets can be trapped. + +## ClaimAsset +```rust,noplayground +ClaimAsset { assets: MultiAssets, ticket: MultiLocation } +``` + +Once assets are trapped, the `ClaimAsset` instruction can be used to claim the assets. The `ClaimAsset` instruction has two fields. + +The `assets` field tells which trapped assets should be claimed. +This must match exactly with the assets claimable by the origin. + +The `ticket` field is an identifier that helps locating the asset. It is, for example, useful for distinguishing between Asset Versions. Lets say we have an XCM V2 trapped asset and send an XCM V3 `ClaimAsset` instruction, then the `ticket` field can be used to tell between the versions. In the xcm-pallet, `Here` is used to describe the same version as the `ClaimAsset` instruction, while the `GeneralIndex` Junction is used to describe other XCM versions. + +## Example +The full example can be found [here](TODO). + +The scenario of the example is this: +Parachain A withdraws funds from its sovereign account on the relay chain. +The assets are trapped because an error is thrown and the execution is halted. +Parachain A claims the trapped assets and receives a report of the holding register. + +Parachain A sends the following message to the relay chain. +The message errors because of the `Trap` instruction, so all assets in the Holding Register are trapped. +```rust, noplayground +let message = Xcm(vec![ + WithdrawAsset((Here, 10 * CENTS).into()), + BuyExecution { fees: (Here, CENTS).into(), weight_limit: WeightLimit::Unlimited }, + Trap(0), // <-- Errors + DepositAsset { // <-- Not executed because of error. + assets: All.into(), + beneficiary: AccountId32 { + network: Some(parachain::RelayNetwork::get()), + id: ALICE.into() + }.into() + } +]); +``` + +Parachain A claims the assets, reports them to itself and deposits them in the Account of Alice. +```rust, noplayground +let claim_message = Xcm(vec![ + ClaimAsset { assets: (Here, 10 * CENTS).into(), ticket: Here.into() }, + ReportHolding { + response_info: QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_parts(1_000_000_000, 64*64) }, + assets: All.into() + }, + DepositAsset { + assets: All.into(), + beneficiary: AccountId32 { + network: Some(parachain::RelayNetwork::get()), + id: ALICE.into() + }.into() + }, +]); +``` \ No newline at end of file From 93da65dabc94cf39cb07af878ec3615b7f7c8707 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Thu, 18 May 2023 10:45:27 +0200 Subject: [PATCH 38/73] Update src/journey/locks/locks.md Co-authored-by: Keith Yeung --- src/journey/locks/locks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/journey/locks/locks.md b/src/journey/locks/locks.md index 1380489..8ab82a0 100644 --- a/src/journey/locks/locks.md +++ b/src/journey/locks/locks.md @@ -117,7 +117,7 @@ The scenario of this example is as follows: Parachain A sets two locks on the relay chain with as unlockers Parachain B and Parachain C. Parachain A then requests Parachain B to partly unlock. -Note: The locks overlap. When there are two or more locks, the total assets that are locked is equal to the most restrictive lock (the lock that locks the most assets). When the most restrictive lock is unlocked, the total locked assets is than equal to the second most restrictive lock. +Note: The locks overlap. When there are two or more locks, the total assets that are locked is equal to the most restrictive lock (the lock that locks the most assets). When the most restrictive lock is unlocked, the total locked assets is than equal to the next most restrictive lock. ![Example](./images/Example2.png) From 0c16492b3704a6e5ece688563446b4323daad8a5 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Thu, 18 May 2023 14:45:16 +0200 Subject: [PATCH 39/73] add ExchangeAsset instruction example --- src/journey/holding-modifiers.md | 57 ++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/journey/holding-modifiers.md b/src/journey/holding-modifiers.md index e60e1d3..78cef6f 100644 --- a/src/journey/holding-modifiers.md +++ b/src/journey/holding-modifiers.md @@ -38,3 +38,60 @@ Response::Assets((Parent, 6 * CENTS).into()) ## ExchangeAsset +```rust,noplayground +ExchangeAsset { give: MultiAssetFilter, want: MultiAssets, maximal: bool } +``` +The `ExchangeAsset` instruction allows us to remove asset(s) (`give`) from the Holding Register and replace them with alternative +assets (`want`). The `ExchangeAsset` instruction has three fields. + +The `give` field indicates the maximum number of assets that can be removed from the Holding register. + +The `want` field indicates the minimum amount of assets which `give` should be exchanged for. We should at a mimimum get the assets in `want` for the execution of the instruction not to fail. + +If the `maximal` field is `true`, then we prefer to give as much as possible up to the limit of `give` +and receive accordingly more assets then stated in `want`. If the `maximal` field is `false`, then we prefer to give as little as possible in +order to receive as little as possible while receiving at least `want`. + +### Example +The full example can be found [here](TODO). + +The scenario for the example is this: +Scenario: +The relay chain sends an XCM to Parachain A that: +.1 Withdraws some native assets +.2 Exchanges these assets for relay chain derivative tokens, with maximal set to true. +.3 Deposit all the assets that are in the Holding in the account of Alice. + +NOTE: The implementation of the AssetExchanger is simple +and in this case swaps all the assets in the exchange for the assets in `give`. +Depending on the implementation of AssetExchanger, the test results could differ. + +The Assets in the exchange in Parachain(1). This is a custom exchange implementation just for testing purposes. +```rust,noplayground +let assets_in_exchange = vec![(Parent, 10 * CENTS).into()]; +parachain::set_exchange_assets(assets_in_exchange); +``` + +The message that is send: +```rust,noplayground +let message = Xcm(vec![ + WithdrawAsset((Here, 10 * CENTS).into()), + BuyExecution { fees: (Here, CENTS).into(), weight_limit: WeightLimit::Unlimited }, + // Maximal field set to true. + ExchangeAsset { + give: Definite((Here, 5 * CENTS).into()), + want: (Parent, 5 * CENTS).into(), + maximal: true, + }, + DepositAsset { + assets: AllCounted(2).into(), + beneficiary: AccountId32 { + network: Some(parachain::RelayNetwork::get()), + id: ALICE.into(), + } + .into(), + }, +]); +``` + +Alice receives `5 CENTS` worth of native assets (`Here`) and `5 CENTS` worth of relay chain derivative assets (`Parent`). \ No newline at end of file From ea383a316d9b00817ccc8bf51c0e83035776e3b6 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Thu, 18 May 2023 17:09:15 +0200 Subject: [PATCH 40/73] add HRMP and exportMessage instructions --- src/SUMMARY.md | 2 +- src/journey/channels-and-bridges.md | 96 +++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/journey/channels-and-bridges.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 9859dfe..a098063 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -29,7 +29,7 @@ - [Queries](journey/queries.md) - [XCM Version](journey/version.md) - [Locks]() - - [Channels]() + - [Channels and Bridges](journey/channels-and-bridges.md) - [Misc]() - [Config Deep Dive](executor_config/README.md) - [Testing]() diff --git a/src/journey/channels-and-bridges.md b/src/journey/channels-and-bridges.md new file mode 100644 index 0000000..b4d4fef --- /dev/null +++ b/src/journey/channels-and-bridges.md @@ -0,0 +1,96 @@ +# Channels +XCM has instructions that aid in the establishment of a HRMP channel between parachains. +HRMP channels are always one way, so every channel has a sender and a recipient. +These instructions are related to system processes, and will normally not be used by developers, like the other instructions. +We still want to list them, as they are part of XCM: + +- `HrmpNewChannelOpenRequest` +- `HrmpChannelAccepted` +- `HrmpChannelClosing` + +## HrmpNewChannelOpenRequest +```rust,noplayground +HrmpNewChannelOpenRequest { + #[codec(compact)] + sender: u32, + #[codec(compact)] + max_message_size: u32, + #[codec(compact)] + max_capacity: u32, +} +``` +The `HrmpNewChannelOpenRequest` is an instruction to notify about a new incoming HRMP channel. +This message is meant to be sent by the relay chain to a parachain. + +The `sender` field represents the ParaId of the parachain initializing the channel. +This parachain will also be the sender in the to-be opened channel. + +The `max_message_size` field is the maximum size of a message that is send through the channel. +This field is the size proposed by the sender, and needs to be accepted by the recipient. + +The `max_capacity` is the maximum number of messages that can be queued in the channel. + +## HrmpChannelAccepted +```rust,noplayground +HrmpChannelAccepted { + #[codec(compact)] + recipient: u32, +} +``` +The `HrmpChannelAccepted` instruction is used 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 parachain. + +The `recipient` field represents the ParaId of the parachain that initialized the channel, so it equals the `sender` field in the preceding `HrmpNewChannelOpenRequest` instruction. + +## HrmpChannelClosing +```rust,noplayground +HrmpChannelClosing { + #[codec(compact)] + initiator: u32, + #[codec(compact)] + sender: u32, + #[codec(compact)] + recipient: u32, +} +``` + +The `HrmpChannelClosing` instruction is used 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. + +The `initiator` field represents the ParaId of the parachain that is closing the channel. +It is equal to either the `sender` or `recipient` field. + +The `sender` field represents the ParaId of the parachain that is the sender side of the channel. + +The `recipient` field represents the ParaId of the parachain that is the recipient side of the channel. + +Important to note is that both the sender and recipient can close the channel. + + +# Message Export (Bridging) + +XCM has an instruction that allows us to send an XCM to a Non-Local Consensus System, meaning to MultiLocation that is outside our current GlobalConsensus. +For example, it allows us to send an XCM from Kusama to Polkadot or from Polkadot to an Ethereum-based chain. +Exporting an XCM to another Non-Local Consensus System will tend to utilize some extra consensus layer/mechanism, the obvious one being a bridge. +The instruction to export an XCM is called the `ExportMessage`. + +## ExportMessage +```rust,noplayground +ExportMessage { network: NetworkId, destination: InteriorMultiLocation, xcm: Xcm<()> }, +``` +The `ExportMessage` instruction can be used to export a message to a Non-Local Consensus System. +The message is sent to the bridge (or other consensus mechanism) that is able to export the message. +It is possible that a fee is charged for exporting the message via the bridge and this may be determined based on the contents of the `xcm` being sent. +These fees are taken from the Holding Register. + +The `network` field is the remote consensus system to which the message should be exported. + +The `destination` field is the location relative to the remote consensus system to which the message should be sent on arrival. + +The `xcm` field is 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`. \ No newline at end of file From 9e1eb6e402d69db5983039ad850e0f1d86b75fc4 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Fri, 19 May 2023 06:49:09 +0200 Subject: [PATCH 41/73] address simple feedback --- src/journey/channels-and-bridges.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/journey/channels-and-bridges.md b/src/journey/channels-and-bridges.md index b4d4fef..ff699f5 100644 --- a/src/journey/channels-and-bridges.md +++ b/src/journey/channels-and-bridges.md @@ -1,7 +1,8 @@ # Channels XCM has instructions that aid in the establishment of a HRMP channel between parachains. -HRMP channels are always one way, so every channel has a sender and a recipient. -These instructions are related to system processes, and will normally not be used by developers, like the other instructions. +HRMP channels are always unidirectional (one-way); every channel has a static sender and a static recipient. +To send messages in the opposite direction (i.e. from recipient to sender), another new HRMP channel must be opened. +Unlike other XCM instructions, these HRMP instructions are related to the underlying transport mechanism, and will normally not be sent by developers. We still want to list them, as they are part of XCM: - `HrmpNewChannelOpenRequest` @@ -75,7 +76,7 @@ Important to note is that both the sender and recipient can close the channel. XCM has an instruction that allows us to send an XCM to a Non-Local Consensus System, meaning to MultiLocation that is outside our current GlobalConsensus. For example, it allows us to send an XCM from Kusama to Polkadot or from Polkadot to an Ethereum-based chain. Exporting an XCM to another Non-Local Consensus System will tend to utilize some extra consensus layer/mechanism, the obvious one being a bridge. -The instruction to export an XCM is called the `ExportMessage`. +The instruction to export an XCM is called `ExportMessage`. ## ExportMessage ```rust,noplayground From a3dbb690a6b0116d6c22258945940af33bc31372 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Fri, 19 May 2023 10:25:30 +0200 Subject: [PATCH 42/73] address feedback on owner --- src/journey/locks/locks.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/journey/locks/locks.md b/src/journey/locks/locks.md index 8ab82a0..32a031f 100644 --- a/src/journey/locks/locks.md +++ b/src/journey/locks/locks.md @@ -36,7 +36,9 @@ The locked assets can only be unlocked by receiving an `UnlockAsset` instruction This instruction requires the following parameters: - `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. +- `owner`: The owner of the asset on the chain in which it was locked. This may be a location specific to the origin network. +The owner can request this origin to unlock the assets using a `RequestUnlock` instruction. +However, the owner is not able to unlock the assets themselves. It is essential to trust the origin to have locked the corresponding asset before sending this message. From 559a1e1a872ea0c6de437c3d6088d84863f44436 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Fri, 19 May 2023 10:27:39 +0200 Subject: [PATCH 43/73] address feedback --- src/journey/channels-and-bridges.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/journey/channels-and-bridges.md b/src/journey/channels-and-bridges.md index ff699f5..b149adb 100644 --- a/src/journey/channels-and-bridges.md +++ b/src/journey/channels-and-bridges.md @@ -84,8 +84,7 @@ ExportMessage { network: NetworkId, destination: InteriorMultiLocation, xcm: Xcm ``` The `ExportMessage` instruction can be used to export a message to a Non-Local Consensus System. The message is sent to the bridge (or other consensus mechanism) that is able to export the message. -It is possible that a fee is charged for exporting the message via the bridge and this may be determined based on the contents of the `xcm` being sent. -These fees are taken from the Holding Register. +A fee is charged for exporting the message via the bridge. The `network` field is the remote consensus system to which the message should be exported. From 343eddf8d4f3216f308f94c0584752a67536b5ae Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 23 May 2023 12:54:14 -0300 Subject: [PATCH 44/73] Add chapters on weight and fees (#9) * Add fundamentals chapter on weight and fees * Address feedback * Finish fees journey chapter * Address feedback * Fix issues with SUMMARY --- src/SUMMARY.md | 8 +- src/fundamentals/fees.md | 1 - src/fundamentals/weight_and_fees.md | 42 +++++++++++ src/fundamentals/xcvm.md | 1 + src/journey/fees/README.md | 112 ++++++++++++++++++++++++++++ src/quickstart/first-look.md | 4 +- 6 files changed, 161 insertions(+), 7 deletions(-) delete mode 100644 src/fundamentals/fees.md create mode 100644 src/fundamentals/weight_and_fees.md create mode 100644 src/fundamentals/xcvm.md create mode 100644 src/journey/fees/README.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index c584929..77e9dee 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -15,15 +15,15 @@ - [Junction(s)](fundamentals/multilocation/junction.md) - [Example](fundamentals/multilocation/example.md) - [MultiAsset](fundamentals/multiasset.md) - - [Responses](fundamentals/responses.md) - - [Fees](fundamentals/fees.md) + - [XCVM](fundamentals/xcvm.md) + - [Weight and fees](fundamentals/weight_and_fees.md) - [A Journey through XCM](journey/README.md) - [Transfers](journey/transfers/README.md) - [Asset teleportation](journey/transfers/teleports.md) - [Reserve-backed transfers](journey/transfers/reserve.md) - [Transact: A general solution](journey/transact.md) - - [Origin]() - - [Fees]() + - [Fee handling](journey/fees/README.md) + - [Origin manipulation](journey/origins.md) - [More register modifiers](journey/register-modifiers.md) - [More Holding Modifiers](./journey/holding-modifiers.md) - [Trap and Claim assets](./journey/trap-and-claim.md) diff --git a/src/fundamentals/fees.md b/src/fundamentals/fees.md deleted file mode 100644 index d39c07d..0000000 --- a/src/fundamentals/fees.md +++ /dev/null @@ -1 +0,0 @@ -Todo \ No newline at end of file diff --git a/src/fundamentals/weight_and_fees.md b/src/fundamentals/weight_and_fees.md new file mode 100644 index 0000000..cf519a3 --- /dev/null +++ b/src/fundamentals/weight_and_fees.md @@ -0,0 +1,42 @@ +# Weight and fees + +The resources available to a blockchain are limited, so it's important to manage how operations on-chain use them. +Not managing how resources are used can open an attack vector, known as DoS (Denial of Service), where an attacker floods the chain with operations in order to get it to stop producing blocks. +In order to manage how resources are used and to protect against DoS attacks, XCM uses a concept of *weight*. +This concept, which has the purpose of quantifying usage of blockchain resources, comes from the [Substrate](https://docs.substrate.io/build/tx-weights-fees/) world. + +Weight is two-dimensional, it tracks both time (execution time) and space (state accesses). +Weight determines how much fees need to be paid in order to perform some operation. +The logic for turning it into fees is configurable. + +Some systems have the concept of *gas metering*, which is calculated during execution and only measures execution time. +Weight, however, is static, defined beforehand, which makes XCM execution lighter by not including gas metering. + +The principle behind weight payment is to pay for what you use, so the two stages of XCM where fees are paid are *sending* the message and actually *executing* it. +The fees for sending are paid on the local system, usually by the origin of the message, because we are using the message delivery mechanism maintained by the origin. +Similarly, the execution fees are paid on the destination system, via the `BuyExecution` instruction. In other words, XCMs are paid for via their own instructions. +We'll talk more about `BuyExecution` in the [fee handling chapter](TODO:add_link). + +XCM is agnostic, which means it doesn't assume fees need to be paid. +It's entirely possible to not pay for the effects of an XCM on the destination system. +Even in systems where fees have to be paid, special cases of free execution can be made. +There are security measures systems can put in place (see [barrier](TODO:add_link)) to not execute XCMs that do not pay for their fees. + +## Executor config + +The executor has a `Weigher` [configuration item](TODO:add_link) that specifies the weight of each instruction. +It weighs the whole message by adding the weight of each instruction. +A simple way of weighing instructions is to assign them a base weight value to all of them. +This works, but it is not very accurate, as different instructions use more resources when being executed. +A better approach is to benchmark each instruction to find out the actual weight used by each. + +Another configuration item, `Trader`, converts the required weight units into fees, which are represented as `MultiAsset`s. +There are two basic approaches: one is to just assign a value (measured in assets) to each unit of weight; the other is to reuse some existing transaction payment method for XCM weight. +Custom configurations allow for things like NFT coupons that give you a certain amount of weight for executing the XCM. + +Naturally, this configuration items allow for any approach you can think of for weighing messages and charging execution fees. + +## XCM pallet + +FRAME pallets, like the XCM pallet, specify weights for each extrinsic they expose. +That means that when interacting with pallets that deal with XCM, there will be an additional fee at the beginning for calling the extrinsic locally. diff --git a/src/fundamentals/xcvm.md b/src/fundamentals/xcvm.md new file mode 100644 index 0000000..bbdf083 --- /dev/null +++ b/src/fundamentals/xcvm.md @@ -0,0 +1 @@ +# XCVM diff --git a/src/journey/fees/README.md b/src/journey/fees/README.md new file mode 100644 index 0000000..73ae80f --- /dev/null +++ b/src/journey/fees/README.md @@ -0,0 +1,112 @@ +# Fee handling + +Like we learnt in the [weight and fees](../../fundamentals/weight_and_fees.md) chapter, the XCM operations our messages perform need to be paid for. +To accomplish this, we'll make use of different instructions in this chapter. + +## BuyExecution + +```rust,noplayground +BuyExecution { fees: MultiAsset, weight_limit: WeightLimit } +``` + +This instruction is used to buy weight using fees. +While in some cases there's no need to pay for execution (if you control both systems for example), in most cases you'll need to add this instruction. +There's a predefined [barrier](../../config/barrier.md), `AllowTopLevelPaidExecutionFrom`, that explicitly drops messages that do not include this instruction. + +Let's grab the teleport message from the [transfers chapter](../transfers/teleports.md) and add fee payment. + +```rust,noplayground +let message = Xcm(vec![ + WithdrawAsset((Here, withdraw_amount + fee_estimation).into()), + BuyExecution { // <-- Added here + fees: (Here, fee_estimation).into(), + weight_limit: WeightLimit::Limited(weight_estimation), + }, + InitiateTeleport { + assets: All.into(), + dest: Parachain(1).into(), + xcm: Xcm(vec![DepositAsset { + assets: All.into(), + beneficiary: Junction::AccountId32 { + network: None, + id: ALICE.into(), + }, + }]), + }, +]); +``` + +`fee_estimation` and `weight_estimation` are values that can be calculated from the configuration of the receiving chain. +As mentioned in the [weight and fees](../../fundamentals/weight_and_fees.md) chapter of the fundamentals, XCMs instructions are usually assigned weights separately, so, in order to estimate the weight, you need to estimate the weight of every instruction and add them together. +By using `WeightLimit::Limited()`, you guarantee the message will error if it tries to use more weight than you expect, if you don't mind this, you can use `WeightLimit::Unlimited`. +The `fee_estimation` value is the maximum assets you want to use, if it doesn't cover all fees, message execution will fail. +You can add a higher value (all of `withdraw_amount` for example) to make sure you have enough assets for fee payment. +If you plan to use the entirety of `withdraw_amount`, however, it's recommended to add a little extra for fee payment. + +In our examples, we use a very simple method, where all instructions weigh a constant value. +This is very useful for testing purposes, but it's recommended to actually benchmark every instruction as they differ in resource usage. +Given our setup, we estimate the weight and fee using only the number of instructions in each message. + +## SetFeesMode + +```rust,noplayground +SetFeesMode { jit_withdraw: bool } +``` + +This instruction changes the fee mode of the XCVM. +If `jit_withdraw` is set to true, then fee assets are taken directly from the origin's on-chain account, instead of the holding register. +This means the fees are taken directly from the account, no need for a `BuyExecution` instruction. +That means you make sure the message will get executed, as long as there are enough assets in the account. +It's useful when paying sending fees, which are difficult to estimate, as they usually depend on network congestion. + +## UnpaidExecution + +```rust,noplayground +UnpaidExecution { weight_limit: WeightLimit, check_origin: Option } +``` + +This instruction is used for explicitly stating this message shouldn't be paid for. +It can be used as a way of identifying certain priviledged messages that don't pay fees, coming from a particular system. +This instruction can be searched for in [barriers](TODO:add_link) to allow this. +Make sure you trust the origin system because it won't be paying fees. +There's already a predefined barrier in xcm-builder, `AllowExplicitUnpaidExecutionFrom`, that makes sure this is the first instruction in the message. +As always, you can build your own for your own use-cases. + +This is safer than allowing all messages from a particular system to not pay fees, as it's an exception to the rule and not the default. +Extra measures can be taken to limit who can use this instruction. + +## RefundSurplus + +```rust,noplayground +RefundSurplus +``` + +Refunds any surplus weight previously bought with `BuyExecution`. +This is useful in many cases: +- When you pay for execution of your whole message, but there's an error and not all instructions get executed +- When you set an error handler, buy weight for it, but in the end there's no error so it doesn't get called +- When you use the [`Transact` instruction]() and the call takes less weight than expected + +### Example + +```rust,noplayground +let message = Xcm(vec![ + WithdrawAsset((Here, amount + fee_estimation).into()), + BuyExecution { + fees: (Here, fee_estimation).into(), + weight_limit: WeightLimit::Limited(weight_estimation), + }, + SetErrorHandler(Xcm(vec![ + RefundSurplus + ])), + DepositAsset { ... }, + DepositAsset { ... }, + DepositAsset { ... }, + DepositAsset { ... }, + DepositAsset { ... }, +]); +``` + +In this example, we pay upfront for all the transactions we do later with the `DepositAsset` instructions. +If any transaction throws an error (for example, due to lack of funds), the error handler will be called and the weight for all the instructions that weren't executed is refunded. +For the full example, check our [examples repo](TODO:add_link). diff --git a/src/quickstart/first-look.md b/src/quickstart/first-look.md index 38c1ea6..98d4471 100644 --- a/src/quickstart/first-look.md +++ b/src/quickstart/first-look.md @@ -1,10 +1,10 @@ # First Look In this section, we take you through a simple example of an XCM. In this example, we withdraw the native token from the account of Alice and deposit this token in the account of Bob. This message simulates a transfer between two accounts in the same consensus system (`ParaA`). Find here the [code example](). ## Message -```rust +```rust,noplayground let message = Xcm(vec![ WithdrawAsset((Here, amount).into()), - BuyExecution{fees: (Here, amount).into(), weight_limit: WeightLimit::Unlimited}, + BuyExecution{ fees: (Here, amount).into(), weight_limit: WeightLimit::Unlimited }, DepositAsset { assets: All.into(), beneficiary: MultiLocation { From 3b2520fa0b799ef656a1bd83fdcc747d2eaf15c1 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 23 May 2023 19:00:06 -0300 Subject: [PATCH 45/73] Add deploy script to github pages (#22) --- .github/workflows/deploy.yml | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..ef4fbd1 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,39 @@ +name: Deploy +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: write # To push a branch + pull-requests: write # To create a PR from that branch + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Install latest mdbook + run: | + tag=$(curl 'https://api.github.com/repos/rust-lang/mdbook/releases/latest' | jq -r '.tag_name') + url="https://github.com/rust-lang/mdbook/releases/download/${tag}/mdbook-${tag}-x86_64-unknown-linux-gnu.tar.gz" + mkdir mdbook + curl -sSL $url | tar -xz --directory=./mdbook + echo `pwd`/mdbook >> $GITHUB_PATH + - name: Deploy GitHub Pages + run: | + # This assumes your book is in the root of your repository. + # Just add a `cd` here if you need to change to another directory. + mdbook build + git worktree add gh-pages + git config user.name "Deploy from CI" + git config user.email "" + cd gh-pages + # Delete the ref to avoid keeping history. + git update-ref -d refs/heads/gh-pages + rm -rf * + mv ../book/* . + git add . + git commit -m "Deploy $GITHUB_SHA to gh-pages" + git push --force --set-upstream origin gh-pages From fef3aa83e6e298afbd2c9bb035846f8a0531e019 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 23 May 2023 19:04:26 -0300 Subject: [PATCH 46/73] Create mdbook.yml --- .github/workflows/mdbook.yml | 60 ++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/workflows/mdbook.yml diff --git a/.github/workflows/mdbook.yml b/.github/workflows/mdbook.yml new file mode 100644 index 0000000..543c435 --- /dev/null +++ b/.github/workflows/mdbook.yml @@ -0,0 +1,60 @@ +# Sample workflow for building and deploying a mdBook site to GitHub Pages +# +# To get started with mdBook see: https://rust-lang.github.io/mdBook/index.html +# +name: Deploy mdBook site to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: ubuntu-latest + env: + MDBOOK_VERSION: 0.4.21 + steps: + - uses: actions/checkout@v3 + - name: Install mdBook + run: | + curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf -y | sh + rustup update + cargo install --version ${MDBOOK_VERSION} mdbook + - name: Setup Pages + id: pages + uses: actions/configure-pages@v3 + - name: Build with mdBook + run: mdbook build + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + path: ./book + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 From c17e0e115501214c341b2b651d7669695749c38b Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 23 May 2023 19:04:54 -0300 Subject: [PATCH 47/73] Delete deploy.yml --- .github/workflows/deploy.yml | 39 ------------------------------------ 1 file changed, 39 deletions(-) delete mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index ef4fbd1..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Deploy -on: - push: - branches: - - main - -jobs: - deploy: - runs-on: ubuntu-latest - permissions: - contents: write # To push a branch - pull-requests: write # To create a PR from that branch - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Install latest mdbook - run: | - tag=$(curl 'https://api.github.com/repos/rust-lang/mdbook/releases/latest' | jq -r '.tag_name') - url="https://github.com/rust-lang/mdbook/releases/download/${tag}/mdbook-${tag}-x86_64-unknown-linux-gnu.tar.gz" - mkdir mdbook - curl -sSL $url | tar -xz --directory=./mdbook - echo `pwd`/mdbook >> $GITHUB_PATH - - name: Deploy GitHub Pages - run: | - # This assumes your book is in the root of your repository. - # Just add a `cd` here if you need to change to another directory. - mdbook build - git worktree add gh-pages - git config user.name "Deploy from CI" - git config user.email "" - cd gh-pages - # Delete the ref to avoid keeping history. - git update-ref -d refs/heads/gh-pages - rm -rf * - mv ../book/* . - git add . - git commit -m "Deploy $GITHUB_SHA to gh-pages" - git push --force --set-upstream origin gh-pages From 863dd57fbb0d210019f81977f456747bcbefecf3 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 23 May 2023 21:21:02 -0300 Subject: [PATCH 48/73] Move examples in, fix links, tweak wording --- examples/.gitignore | 16 + examples/Cargo.toml | 68 ++ examples/LICENSE | 674 ++++++++++++++++++ examples/README.md | 2 + examples/rustfmt.toml | 21 + examples/src/expects/mod.rs | 238 +++++++ examples/src/first_look.rs | 47 ++ examples/src/forum.rs | 65 ++ examples/src/holding_modifiers/mod.rs | 137 ++++ .../src/kusama_test_net/kusama_test_net.rs | 176 +++++ examples/src/kusama_test_net/mod.rs | 2 + examples/src/kusama_test_net/yayoi.rs | 237 ++++++ examples/src/lib.rs | 12 + examples/src/locks/mod.rs | 131 ++++ examples/src/origins/mod.rs | 37 + examples/src/queries/mod.rs | 194 +++++ examples/src/simple_test_net/asset_hub.rs | 530 ++++++++++++++ examples/src/simple_test_net/mod.rs | 158 ++++ examples/src/simple_test_net/parachain.rs | 543 ++++++++++++++ examples/src/simple_test_net/relay_chain.rs | 256 +++++++ examples/src/transact/mod.rs | 95 +++ examples/src/transfers/mod.rs | 2 + examples/src/transfers/reserve.rs | 108 +++ examples/src/transfers/teleport.rs | 125 ++++ examples/src/trap_and_claim/mod.rs | 74 ++ examples/src/version_subscription/mod.rs | 42 ++ src/SUMMARY.md | 4 +- src/fundamentals/README.md | 6 +- src/fundamentals/xcvm.md | 7 + src/journey/expects.md | 17 +- src/journey/fees/README.md | 8 +- src/journey/holding-modifiers.md | 4 +- src/journey/locks/locks.md | 4 +- src/journey/origins.md | 3 +- src/journey/queries.md | 12 +- src/journey/register-modifiers.md | 4 +- src/journey/transact.md | 7 +- src/journey/transfers/README.md | 8 +- src/journey/transfers/reserve.md | 2 +- src/journey/transfers/teleports.md | 2 +- src/journey/version.md | 6 +- src/overview/README.md | 7 +- src/overview/architecture.md | 7 +- src/overview/format.md | 4 +- src/overview/interoperability.md | 2 +- src/overview/xcvm.md | 11 +- src/quickstart/README.md | 9 +- src/quickstart/first-look.md | 37 +- src/quickstart/xcm-simulator.md | 18 +- src/testing/README.md | 13 + src/transport_protocols/README.md | 8 + src/xcm.md | 11 +- 52 files changed, 4131 insertions(+), 80 deletions(-) create mode 100644 examples/.gitignore create mode 100644 examples/Cargo.toml create mode 100644 examples/LICENSE create mode 100644 examples/README.md create mode 100644 examples/rustfmt.toml create mode 100644 examples/src/expects/mod.rs create mode 100644 examples/src/first_look.rs create mode 100644 examples/src/forum.rs create mode 100644 examples/src/holding_modifiers/mod.rs create mode 100644 examples/src/kusama_test_net/kusama_test_net.rs create mode 100644 examples/src/kusama_test_net/mod.rs create mode 100644 examples/src/kusama_test_net/yayoi.rs create mode 100644 examples/src/lib.rs create mode 100644 examples/src/locks/mod.rs create mode 100644 examples/src/origins/mod.rs create mode 100644 examples/src/queries/mod.rs create mode 100644 examples/src/simple_test_net/asset_hub.rs create mode 100644 examples/src/simple_test_net/mod.rs create mode 100644 examples/src/simple_test_net/parachain.rs create mode 100644 examples/src/simple_test_net/relay_chain.rs create mode 100644 examples/src/transact/mod.rs create mode 100644 examples/src/transfers/mod.rs create mode 100644 examples/src/transfers/reserve.rs create mode 100644 examples/src/transfers/teleport.rs create mode 100644 examples/src/trap_and_claim/mod.rs create mode 100644 examples/src/version_subscription/mod.rs create mode 100644 src/testing/README.md create mode 100644 src/transport_protocols/README.md diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..b7e28fc --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,16 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + + +# Added by cargo + +/target +/Cargo.lock diff --git a/examples/Cargo.toml b/examples/Cargo.toml new file mode 100644 index 0000000..63137d0 --- /dev/null +++ b/examples/Cargo.toml @@ -0,0 +1,68 @@ +[package] +name = "xcm-examples" +version = "0.1.0" +edition = "2021" +authors = ["Xcm Team"] + + +[dependencies] +bounded-collections = { version = "0.1.5", default-features = false } +smallvec = "1.4.0" +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive", "max-encoded-len"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde_json = { version = "1.0.85" } +hex = { version = "0.4" } +hex-literal = { version = "0.3.1" } +libsecp256k1 = { version = "0.7" } + +#Polkadot +polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } +xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } +xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } +xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } +pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39"} +pallet-assets = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +pallet-uniques = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +pallet-nfts = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } +polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } +kusama-runtime = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } +kusama-runtime-constants = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } +xcm-simulator = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } +# substrate +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +sp-session = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +sp-version = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } + +cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } +cumulus-primitives-utility = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } +cumulus-primitives-timestamp = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } +cumulus-pallet-parachain-system = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } +cumulus-pallet-dmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } +cumulus-pallet-xcmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } +cumulus-pallet-xcm = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } +parachain-info = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } +cumulus-primitives-parachain-inherent = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } +cumulus-test-relay-sproof-builder = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } +statemine-runtime = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } + +xcm-emulator = { git = "https://github.com/shaunxw/xcm-simulator", rev = "aa13dce47596e150806dfc3af99096dae6ffc65e" } + +[dev-dependencies] +env_logger = "0.9.0" +log = "0.4.17" +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +sp-trie = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } + + diff --git a/examples/LICENSE b/examples/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/examples/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program 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. + + This program 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 this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..b65cada --- /dev/null +++ b/examples/README.md @@ -0,0 +1,2 @@ +# xcm-examples +This repository shows the xcm examples for the xcm docs diff --git a/examples/rustfmt.toml b/examples/rustfmt.toml new file mode 100644 index 0000000..44d6c5a --- /dev/null +++ b/examples/rustfmt.toml @@ -0,0 +1,21 @@ +# Basic +hard_tabs = true +max_width = 100 +use_small_heuristics = "Max" +# Imports +imports_granularity = "Crate" +reorder_imports = true +# Consistency +newline_style = "Unix" +# Misc +chain_width = 80 +spaces_around_ranges = false +binop_separator = "Back" +reorder_impl_items = false +match_arm_leading_pipes = "Preserve" +match_arm_blocks = false +match_block_trailing_comma = true +trailing_comma = "Vertical" +trailing_semicolon = false +use_field_init_shorthand = true +edition = "2021" \ No newline at end of file diff --git a/examples/src/expects/mod.rs b/examples/src/expects/mod.rs new file mode 100644 index 0000000..976d9a5 --- /dev/null +++ b/examples/src/expects/mod.rs @@ -0,0 +1,238 @@ +#[cfg(test)] +mod tests { + use crate::simple_test_net::*; + use bounded_collections::BoundedVec; + use codec::Encode; + use frame_support::{assert_ok, pallet_prelude::Weight}; + use xcm::latest::prelude::*; + use xcm_simulator::TestExt; + + const AMOUNT: u128 = 10; + const QUERY_ID: u64 = 1234; + + /// Scenario: + /// Parachain wants to execute specific instructions on the relay chain that use assets in the holding register. + /// Before executing these instructions it want to check if the assets in the holding register are expected. + /// If the assets are not expected, it wants to be notified with an `ExpectationFalse` error. + /// It first sets an error handler that reports back an error using the `ReportError` instruction. + /// And adds a `ExpectAsset` instruction just before executing the specific instructions. + #[test] + fn expect_asset() { + MockNet::reset(); + ParaA::execute_with(|| { + let message = Xcm(vec![ + WithdrawAsset((Here, AMOUNT).into()), + BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + // Set the instructions that are executed when ExpectAsset does not pass. + // In this case, reporting back an error to the Parachain. + SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), + })])), + ExpectAsset((Here, AMOUNT + 10).into()), + // Add Instructions that do something with assets in holding when ExpectAsset passes. + ]); + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); + }); + + // Check that QueryResponse message with ExpectationFalse error was received. + ParaA::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![QueryResponse { + query_id: QUERY_ID, + response: Response::ExecutionResult(Some((3, XcmError::ExpectationFalse))), + max_weight: Weight::from_all(0), + querier: Some(Here.into()), + }])], + ); + }); + } + + /// Scenario: + /// Parachain wants to make sure that XcmContext contains the expected `origin` at a certain point during execution. + /// It sets the `ExpectOrigin` instruction to check for the expected `origin`. + /// If the origin is not as expected, the instruction errors, and the ErrorHandler reports back the corresponding error to the parachain. + #[test] + fn expect_origin() { + MockNet::reset(); + + ParaA::execute_with(|| { + let message = Xcm(vec![ + WithdrawAsset((Here, AMOUNT).into()), + BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + // Set the instructions that are executed when ExpectOrigin does not pass. + // In this case, reporting back an error to the Parachain. + SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), + })])), + ClearOrigin, + // Checks if the XcmContext origin is `Parachain(1). + ExpectOrigin(Some(Parachain(1).into())), + ]); + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); + }); + + // Check that QueryResponse message with ExpectationFalse error was received. + ParaA::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![QueryResponse { + query_id: QUERY_ID, + response: Response::ExecutionResult(Some((4, XcmError::ExpectationFalse))), + max_weight: Weight::from_all(0), + querier: None, + }])], + ); + }); + } + + /// Scenario: + /// Parachain wants to make sure that the relay chain has configured a specific pallet with a specific version. + /// It sets the `ExpectPallet` instruction to check for the expected pallet. + /// If the pallet is not as expected, the instruction errors, and the ErrorHandler reports back the corresponding error to the parachain. + #[test] + fn expect_pallet() { + MockNet::reset(); + + ParaA::execute_with(|| { + let message = Xcm(vec![ + WithdrawAsset((Here, AMOUNT).into()), + BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + // Set the instructions that are executed when ExpectPallet does not pass. + // In this case, reporting back an error to the Parachain. + SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), + })])), + // Configured pallet has different `crate_major` so `VersionIncompatible` error is thrown. + ExpectPallet { + index: 1, + name: "Balances".into(), + module_name: "pallet_balances".into(), + crate_major: 3, + min_crate_minor: 0, + }, + // Could execute pallet specific instructions after the expect pallet succeeds. + ]); + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); + }); + + // Check that QueryResponse message with `VersionIncompatible` error was received. + // Can also be a different error based on the pallet mismatch (i.e. PalletNotFound, NameMismatch). + ParaA::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![QueryResponse { + query_id: QUERY_ID, + response: Response::ExecutionResult(Some((3, XcmError::VersionIncompatible))), + max_weight: Weight::from_all(0), + querier: Some(Here.into()), + }])], + ); + }); + } + + /// Scenario: + /// Parachain wants to make sure that the `ErrorHandler` that it sets is only executed when a specific error is thrown. + /// It sets an `ExpectError` instruction in the `SetErrorHandler` to check for the specific error. + /// If a different Error is thrown, the `ErrorHandler` execution is halted. + /// + /// Asserts that the ExpectPallet instruction throws an `PalletNotFound` error instead of the expected `VersionIncompatible` error. + #[test] + fn expect_error() { + MockNet::reset(); + + ParaA::execute_with(|| { + let message = Xcm(vec![ + WithdrawAsset((Here, AMOUNT).into()), + BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + // ReportError is only executed if the thrown error is the `VersionIncompatible` error. + SetErrorHandler(Xcm(vec![ + ExpectError(Some((1, XcmError::VersionIncompatible))), + ReportError(QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), + }), + ])), + // Pallet index is wrong, so throws `PalletNotFound` error. + ExpectPallet { + index: 100, + name: "Balances".into(), + module_name: "pallet_balances".into(), + crate_major: 4, + min_crate_minor: 0, + }, + ]); + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); + }); + + // Does not receive a message as the incorrect error was thrown during execution. + ParaA::execute_with(|| { + assert_eq!(parachain::MsgQueue::received_dmp(), vec![],); + }); + } + + /// Scenario: + /// Parachain wants to make sure that the `Transact` instruction succeeded as it does not throw an XcmError. + /// It sets an `ExpectTransactStatus` instruction to MaybeErrorCode::Success to check if the transact succeeded. + /// If the status was not succesful, the `ExpectTransactStatus` errors, + /// and the ErrorHandler will report the error back to the Parachain. + /// + /// Assert that `set_balance` execution fails as it requires the origin to be root, + /// and the origin_kind is `SovereignAccount`. + #[test] + fn expect_transact_status() { + MockNet::reset(); + // Runtime call dispatched by the Transact instruction. + // set_balance requires root origin. + let call = relay_chain::RuntimeCall::Balances(pallet_balances::Call::< + relay_chain::Runtime, + >::set_balance { + who: ALICE, + new_free: 100, + new_reserved: 0, + }); + + let message = Xcm(vec![ + WithdrawAsset((Here, AMOUNT).into()), + BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + SetErrorHandler(Xcm(vec![ReportTransactStatus(QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), + })])), + Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + call: call.encode().into(), + }, + ExpectTransactStatus(MaybeErrorCode::Success), + ]); + + ParaA::execute_with(|| { + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); + }); + + // The execution of set_balance does not succeed, and error is reported back to the parachain. + ParaA::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![QueryResponse { + query_id: QUERY_ID, + response: Response::DispatchResult(MaybeErrorCode::Error( + // The 2 is the scale encoded Error from the balances pallet + BoundedVec::truncate_from(vec![2]) + )), + max_weight: Weight::from_all(0), + querier: Some(Here.into()), + }])], + ); + }); + } +} diff --git a/examples/src/first_look.rs b/examples/src/first_look.rs new file mode 100644 index 0000000..a7bbe2d --- /dev/null +++ b/examples/src/first_look.rs @@ -0,0 +1,47 @@ +#[cfg(test)] +mod tests { + // use crate::test_net::kusama_test_net::*; + use crate::simple_test_net::*; + use frame_support::assert_ok; + pub use xcm::latest::prelude::*; + use xcm_simulator::TestExt; + + #[test] + fn para_a_simple_transfer() { + MockNet::reset(); + + ParaA::execute_with(|| { + // Amount to transfer. + let amount: u128 = 10; + // Check that the balance of Alice is equal to the `INITIAL_BALANCE`. + assert_eq!(ParachainBalances::free_balance(&ALICE), INITIAL_BALANCE); + + // The XCM used to transfer funds from Alice to Bob. + let message = Xcm(vec![ + WithdrawAsset((Here, amount).into()), + BuyExecution { fees: (Here, amount).into(), weight_limit: WeightLimit::Unlimited }, + DepositAsset { + assets: All.into(), + beneficiary: MultiLocation { + parents: 0, + interior: Junction::AccountId32 { network: None, id: BOB.clone().into() } + .into(), + } + .into(), + }, + ]); + + // Execution of the XCM Instructions in the local consensus system. + // The RuntimeOrigin is Alice, so Alice's account will be used for the WithdrawAsset. + assert_ok!(ParachainPalletXcm::execute( + parachain::RuntimeOrigin::signed(ALICE), + Box::new(xcm::VersionedXcm::from(message.clone())), + 10.into() + )); + + // Check if the funds are subtracted from the account of Alice and added to the account of Bob. + assert_eq!(ParachainBalances::free_balance(ALICE), INITIAL_BALANCE - amount); + assert_eq!(ParachainBalances::free_balance(BOB), amount); + }); + } +} diff --git a/examples/src/forum.rs b/examples/src/forum.rs new file mode 100644 index 0000000..28d47f1 --- /dev/null +++ b/examples/src/forum.rs @@ -0,0 +1,65 @@ +#[cfg(test)] +mod tests { + use crate::simple_test_net::*; + use frame_support::assert_ok; + use xcm::v3::prelude::*; + use xcm_simulator::TestExt; + + const BOB: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([2u8; 32]); + const QUERY_ID: u64 = 1234; + + /// Scenario: + /// ALICE sends message from parachain A to parachain B + /// Goal is to move both A's native asset and B's native asset to BOB + fn forum() { + MockNet::reset(); + + let amount_a = 50 * CENTS; + let amount_b = 50 * CENTS; + + let message: Xcm<()> = Xcm(vec![ + SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { + destination: ParentThen(X1(Parachain(1))).into(), + query_id: QUERY_ID, + max_weight: 0.into(), + })])), + ReserveAssetDeposited((ParentThen(X1(Parachain(1))), amount_a).into()), + WithdrawAsset((Here, amount_b).into()), + ClearOrigin, + BuyExecution { fees: (Here, amount_b).into(), weight_limit: WeightLimit::Unlimited }, + DepositAsset { + assets: AllCounted(2).into(), + beneficiary: Junction::AccountId32 { network: None, id: BOB.clone().into() }.into(), + }, + ]); + + let destination: MultiLocation = (Parent, Parachain(2)).into(); + let alice = Junction::AccountId32 { id: ALICE.into(), network: None }; + + ParaA::execute_with(|| { + assert_ok!(parachain::PolkadotXcm::send_xcm(alice, destination, message)); + + // ALICE on ParaA does not give up any balance + assert_eq!(parachain::Balances::free_balance(ALICE), INITIAL_BALANCE); + }); + + ParaA::execute_with(|| { + dbg!(¶chain::MsgQueue::received_dmp()); + }); + + ParaB::execute_with(|| { + dbg!(parachain::Balances::free_balance(sibling_account_sovereign_account_id(1, ALICE))); + + // ALICE does give up balance of ParaB's native asset... + assert_eq!( + parachain::Balances::free_balance(sibling_account_sovereign_account_id(1, ALICE)), + INITIAL_BALANCE - amount_b + ); + // ...and gives it to BOB + assert_eq!(parachain::Balances::free_balance(BOB), amount_b); + + // ParaA's native asset is minted and deposited to BOB's account + assert_eq!(parachain::Assets::balance(1, &BOB), amount_a); + }); + } +} diff --git a/examples/src/holding_modifiers/mod.rs b/examples/src/holding_modifiers/mod.rs new file mode 100644 index 0000000..e22d329 --- /dev/null +++ b/examples/src/holding_modifiers/mod.rs @@ -0,0 +1,137 @@ +#[cfg(test)] +mod tests { + use crate::simple_test_net::{parachain::RelayNativeAsset, *}; + use frame_support::{assert_ok, pallet_prelude::Weight}; + use xcm::latest::prelude::*; + use xcm_simulator::TestExt; + + const QUERY_ID: u64 = 1234; + + /// Scenario: + /// Parachain A withdraws funds from its sovereign account on the relay chain and burns part of them. + /// The relay chain then reports back the status of the Holding Register to Parachain A. + #[test] + fn burn_assets() { + let message = Xcm(vec![ + WithdrawAsset((Here, 10 * CENTS).into()), + BuyExecution { fees: (Here, CENTS).into(), weight_limit: WeightLimit::Unlimited }, + BurnAsset((Here, 4 * CENTS).into()), + ReportHolding { + response_info: QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_parts(1_000_000_000, 64 * 64), + }, + assets: All.into(), + }, + ]); + ParaA::execute_with(|| { + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); + }); + + ParaA::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![QueryResponse { + query_id: QUERY_ID, + response: Response::Assets((Parent, 6 * CENTS).into()), + max_weight: Weight::from_parts(1_000_000_000, 64 * 64), + querier: Some(Here.into()), + }])], + ) + }); + } + + /// Scenario: + /// The relay chain sends an XCM to Parachain A that: + /// 1) Withdraws some native assets + /// 2) Exchanges these assets for relay chain derivative tokens, with maximal set to true. + /// 3) Deposit all the assets that are in the Holding in the account of Alice. + /// + /// NOTE: The implementation of the AssetExchanger is simple + /// and in this case swaps all the assets in the exchange for the assets in `give`. + /// Depending on the implementation of AssetExchanger, the test results could differ. + #[test] + fn exchange_asset_maximal_true() { + // Exchange contains 10 CENTS worth of parachain A's derivative of the relay token + let assets_in_exchange = vec![(Parent, 10 * CENTS).into()]; + parachain::set_exchange_assets(assets_in_exchange); + + let message = Xcm(vec![ + WithdrawAsset((Here, 10 * CENTS).into()), + BuyExecution { fees: (Here, CENTS).into(), weight_limit: WeightLimit::Unlimited }, + // Maximal field set to true. + ExchangeAsset { + give: Definite((Here, 5 * CENTS).into()), + want: (Parent, 5 * CENTS).into(), + maximal: true, + }, + DepositAsset { + assets: AllCounted(2).into(), + beneficiary: AccountId32 { + network: Some(parachain::RelayNetwork::get()), + id: ALICE.into(), + } + .into(), + }, + ]); + + Relay::execute_with(|| { + assert_ok!(RelaychainPalletXcm::send_xcm(Here, Parachain(1), message.clone())); + }); + + ParaA::execute_with(|| { + assert_eq!(parachain::exchange_assets(), vec![(Here, 5 * CENTS).into()].into()); + assert_eq!(ParachainAssets::balance(1u128, &ALICE), INITIAL_BALANCE + 10 * CENTS); + assert_eq!(ParachainBalances::free_balance(ALICE), INITIAL_BALANCE + 5 * CENTS); + }) + } + + /// Scenario: + /// The relay chain sends an XCM to Parachain A that: + /// 1) Withdraws some native assets + /// 2) Exchanges these assets for relay chain derivative tokens, with maximal set to false. + /// 3) Deposit all the assets that are in the Holding in the account of Alice. + /// + /// NOTE: The implementation of the AssetExchanger is simple + /// and in this case swaps all the assets in the exchange for the assets in `give`. + /// Depending on the implementation of AssetExchanger, the test results could differ. + #[test] + fn exchange_asset_maximal_false() { + // Exchange contains 10 CENTS worth of parachain A's derivative of the relay token + let assets_in_exchange = vec![(Parent, 10 * CENTS).into()]; + parachain::set_exchange_assets(assets_in_exchange); + + let message = Xcm(vec![ + WithdrawAsset((Here, 10 * CENTS).into()), + BuyExecution { fees: (Here, CENTS).into(), weight_limit: WeightLimit::Unlimited }, + // Maximal field set to false. + ExchangeAsset { + give: Definite((Here, 5 * CENTS).into()), + want: (Parent, 5 * CENTS).into(), + maximal: false, + }, + DepositAsset { + assets: AllCounted(2).into(), + beneficiary: AccountId32 { + network: Some(parachain::RelayNetwork::get()), + id: ALICE.into(), + } + .into(), + }, + ]); + + Relay::execute_with(|| { + assert_ok!(RelaychainPalletXcm::send_xcm(Here, Parachain(1), message.clone())); + }); + + ParaA::execute_with(|| { + assert_eq!( + parachain::exchange_assets(), + vec![(Parent, 5 * CENTS).into(), (Here, 5 * CENTS).into()].into() + ); + assert_eq!(ParachainAssets::balance(1u128, &ALICE), INITIAL_BALANCE + 5 * CENTS); + assert_eq!(ParachainBalances::free_balance(ALICE), INITIAL_BALANCE + 5 * CENTS); + }) + } +} diff --git a/examples/src/kusama_test_net/kusama_test_net.rs b/examples/src/kusama_test_net/kusama_test_net.rs new file mode 100644 index 0000000..5c3153c --- /dev/null +++ b/examples/src/kusama_test_net/kusama_test_net.rs @@ -0,0 +1,176 @@ +use crate::kusama_test_net::yayoi; +pub use codec::{Decode, Encode}; +use frame_support::{pallet_prelude::Weight, traits::GenesisBuild}; +use polkadot_primitives::v2::{BlockNumber, MAX_CODE_SIZE, MAX_POV_SIZE}; +use polkadot_runtime_parachains::configuration::HostConfiguration; +use sp_runtime::AccountId32; +pub use xcm::v3::prelude::*; +use xcm_emulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; +use xcm_executor::traits::Convert; + +pub const ALICE: AccountId32 = AccountId32::new([0u8; 32]); +#[allow(dead_code)] +pub const BOB: AccountId32 = AccountId32::new([1u8; 32]); +pub const INITIAL_BALANCE: u128 = 1_000_000_000_000; + +decl_test_relay_chain! { + pub struct KusamaNet { + Runtime = kusama_runtime::Runtime, + XcmConfig = kusama_runtime::xcm_config::XcmConfig, + new_ext = kusama_ext(), + } +} + +decl_test_parachain! { + pub struct Statemine { + Runtime = statemine_runtime::Runtime, + RuntimeOrigin = statemine_runtime::RuntimeOrigin, + XcmpMessageHandler = statemine_runtime::XcmpQueue, + DmpMessageHandler = statemine_runtime::DmpQueue, + new_ext = statemine_ext(1000), + } +} + +decl_test_parachain! { + pub struct SimpleParachain { + Runtime = yayoi::Runtime, + RuntimeOrigin = yayoi::RuntimeOrigin, + XcmpMessageHandler = yayoi::XcmpQueue, + DmpMessageHandler = yayoi::DmpQueue, + new_ext = yayoi_ext(1001), + } +} + +decl_test_parachain! { + pub struct SimpleParachain2 { + Runtime = yayoi::Runtime, + RuntimeOrigin = yayoi::RuntimeOrigin, + XcmpMessageHandler = yayoi::XcmpQueue, + DmpMessageHandler = yayoi::DmpQueue, + new_ext = yayoi_ext(1002), + } +} + +decl_test_network! { + pub struct TestNet { + relay_chain = KusamaNet, + parachains = vec![ + (1000, Statemine), + (1001, SimpleParachain), + (1002, SimpleParachain2), + ], + } +} + +fn default_parachains_host_configuration() -> HostConfiguration { + HostConfiguration { + minimum_validation_upgrade_delay: 5, + validation_upgrade_cooldown: 5u32, + validation_upgrade_delay: 5, + code_retention_period: 1200, + max_code_size: MAX_CODE_SIZE, + max_pov_size: MAX_POV_SIZE, + max_head_data_size: 32 * 1024, + group_rotation_frequency: 20, + chain_availability_period: 4, + thread_availability_period: 4, + max_upward_queue_count: 8, + max_upward_queue_size: 1024 * 1024, + max_downward_message_size: 1024, + ump_service_total_weight: Weight::from_ref_time(4 * 1_000_000_000), + max_upward_message_size: 50 * 1024, + max_upward_message_num_per_candidate: 5, + hrmp_sender_deposit: 0, + hrmp_recipient_deposit: 0, + hrmp_channel_max_capacity: 8, + hrmp_channel_max_total_size: 8 * 1024, + hrmp_max_parachain_inbound_channels: 4, + hrmp_max_parathread_inbound_channels: 4, + hrmp_channel_max_message_size: 1024 * 1024, + hrmp_max_parachain_outbound_channels: 4, + hrmp_max_parathread_outbound_channels: 4, + hrmp_max_message_num_per_candidate: 5, + dispute_period: 6, + no_show_slots: 2, + n_delay_tranches: 25, + needed_approvals: 2, + relay_vrf_modulo_samples: 2, + zeroth_delay_tranche_width: 0, + ..Default::default() + } +} + +pub fn kusama_ext() -> sp_io::TestExternalities { + use kusama_runtime::{Runtime, System}; + + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + pallet_balances::GenesisConfig:: { balances: vec![(ALICE, INITIAL_BALANCE)] } + .assimilate_storage(&mut t) + .unwrap(); + + polkadot_runtime_parachains::configuration::GenesisConfig:: { + config: default_parachains_host_configuration(), + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub fn statemine_ext(para_id: u32) -> sp_io::TestExternalities { + use statemine_runtime::{Runtime, System}; + + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let parachain_info_config = parachain_info::GenesisConfig { parachain_id: para_id.into() }; + + >::assimilate_storage( + ¶chain_info_config, + &mut t, + ) + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (ALICE, INITIAL_BALANCE), + (statemine_sibling_account_id(1001), INITIAL_BALANCE), + (statemine_sibling_account_id(1002), INITIAL_BALANCE), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub fn yayoi_ext(para_id: u32) -> sp_io::TestExternalities { + use crate::kusama_test_net::yayoi::{Runtime, System}; + + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let parachain_info_config = parachain_info::GenesisConfig { parachain_id: para_id.into() }; + + >::assimilate_storage( + ¶chain_info_config, + &mut t, + ) + .unwrap(); + + pallet_balances::GenesisConfig:: { balances: vec![(ALICE, INITIAL_BALANCE)] } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub fn statemine_sibling_account_id(para: u32) -> sp_runtime::AccountId32 { + statemine_runtime::xcm_config::LocationToAccountId::convert((Parent, Parachain(para)).into()) + .unwrap() +} diff --git a/examples/src/kusama_test_net/mod.rs b/examples/src/kusama_test_net/mod.rs new file mode 100644 index 0000000..0c45b13 --- /dev/null +++ b/examples/src/kusama_test_net/mod.rs @@ -0,0 +1,2 @@ +pub mod kusama_test_net; +pub mod yayoi; diff --git a/examples/src/kusama_test_net/yayoi.rs b/examples/src/kusama_test_net/yayoi.rs new file mode 100644 index 0000000..5271816 --- /dev/null +++ b/examples/src/kusama_test_net/yayoi.rs @@ -0,0 +1,237 @@ +use frame_support::{ + construct_runtime, parameter_types, + traits::{ConstU32, Everything, Nothing}, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, +}; +use frame_system::EnsureRoot; +use pallet_xcm::XcmPassthrough; +use polkadot_parachain::primitives::Sibling; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{Convert, IdentityLookup}, + AccountId32, +}; +use xcm::v3::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowUnpaidExecutionFrom, CurrencyAdapter, EnsureXcmOrigin, + FixedWeightBounds, IsConcrete, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, +}; +use xcm_executor::{Config, XcmExecutor}; + +pub type AccountId = AccountId32; +pub type Balance = u128; + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +impl parachain_info::Config for Runtime {} + +parameter_types! { + pub const RelayLocation: MultiLocation = MultiLocation::parent(); + pub const TokenLocation: MultiLocation = Here.into_location(); + pub const RelayNetwork: NetworkId = NetworkId::Kusama; + pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); + pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(RelayNetwork::get()), Parachain(ParachainInfo::parachain_id().into())); +} + +pub type LocationToAccountId = ( + ParentIsPreset, + SiblingParachainConvertsVia, + AccountId32Aliases, +); + +pub type XcmOriginToCallOrigin = ( + SovereignSignedViaLocation, + RelayChainAsNative, + SiblingParachainAsNative, + SignedAccountId32AsNative, + XcmPassthrough, +); + +parameter_types! { + pub const UnitWeightCost: u64 = 10; + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; +} + +pub type LocalAssetTransactor = + CurrencyAdapter, LocationToAccountId, AccountId, ()>; + +/// The means for routing XCM messages which are not for local execution into +/// the right message queues. +pub type XcmRouter = ( + // Two routers - use UMP to communicate with the relay chain: + cumulus_primitives_utility::ParentAsUmp, + // ..and XCMP to communicate with the sibling chains. + XcmpQueue, +); + +pub type Barrier = AllowUnpaidExecutionFrom; + +pub struct XcmConfig; +impl Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = XcmOriginToCallOrigin; + type IsReserve = (); + type IsTeleporter = (); + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = (); + type ResponseHandler = (); + type AssetTrap = (); + type AssetClaims = (); + type SubscriptionService = (); + type AssetLocker = PolkadotXcm; + type AssetExchanger = (); + type PalletInstancesInfo = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = Weight::from_ref_time(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4)); + pub const ReservedDmpWeight: Weight = Weight::from_ref_time(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4)); +} + +impl cumulus_pallet_parachain_system::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnSystemEvent = (); + type SelfParaId = ParachainInfo; + type DmpMessageHandler = DmpQueue; + type ReservedDmpWeight = ReservedDmpWeight; + type OutboundXcmpMessageSource = XcmpQueue; + type XcmpMessageHandler = XcmpQueue; + type ReservedXcmpWeight = ReservedXcmpWeight; + type CheckAssociatedRelayNumber = cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +} + +impl cumulus_pallet_xcmp_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; + type ChannelInfo = ParachainSystem; + type VersionWrapper = (); + type ExecuteOverweightOrigin = EnsureRoot; + type ControllerOrigin = EnsureRoot; + type ControllerOriginConverter = XcmOriginToCallOrigin; + type WeightInfo = (); + type PriceForSiblingDelivery = (); +} + +impl cumulus_pallet_dmp_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; + type ExecuteOverweightOrigin = EnsureRoot; +} + +impl cumulus_pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Nothing; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = (); + type MaxLockers = ConstU32<8>; + type WeightInfo = pallet_xcm::TestWeightInfo; +} + +pub struct AccountIdToMultiLocation; +impl Convert for AccountIdToMultiLocation { + fn convert(account: AccountId) -> MultiLocation { + X1(Junction::AccountId32 { network: None, id: account.into() }).into() + } +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + ParachainSystem: cumulus_pallet_parachain_system::{Pallet, Call, Storage, Inherent, Config, Event}, + ParachainInfo: parachain_info::{Pallet, Storage, Config}, + XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event}, + DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event}, + CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin}, + PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin}, + } +); diff --git a/examples/src/lib.rs b/examples/src/lib.rs new file mode 100644 index 0000000..072e1ae --- /dev/null +++ b/examples/src/lib.rs @@ -0,0 +1,12 @@ +mod expects; +mod first_look; +mod holding_modifiers; +mod kusama_test_net; +mod locks; +mod origins; +mod queries; +mod simple_test_net; +mod transact; +mod transfers; +mod trap_and_claim; +mod version_subscription; diff --git a/examples/src/locks/mod.rs b/examples/src/locks/mod.rs new file mode 100644 index 0000000..6d1d404 --- /dev/null +++ b/examples/src/locks/mod.rs @@ -0,0 +1,131 @@ +#[cfg(test)] +mod tests { + use crate::simple_test_net::*; + use frame_support::{assert_ok, pallet_prelude::Weight}; + use pallet_balances::{BalanceLock, Reasons}; + use xcm::latest::prelude::*; + use xcm_simulator::TestExt; + + /// Scenario: + /// Parachain A locks 5 Cents of relay chain native assets of its Sovereign account on the relay chain and assigns Parachain B as unlocker. + /// Parachain A then asks Parachain B to unlock the funds partly. Parachain B responds by sending an UnlockAssets instruction to the relay chain. + #[test] + fn remote_locking_on_relay() { + MockNet::reset(); + + ParaA::execute_with(|| { + let message = Xcm(vec![LockAsset { + asset: (Here, 5 * CENTS).into(), + unlocker: (Parachain(2)).into(), + }]); + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); + }); + + Relay::execute_with(|| { + assert_eq!( + relay_chain::Balances::locks(¶chain_sovereign_account_id(1)), + vec![BalanceLock { id: *b"py/xcmlk", amount: 5 * CENTS, reasons: Reasons::All }] + ); + }); + + ParaB::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![NoteUnlockable { + owner: (Parent, Parachain(1)).into(), + asset: (Parent, 5 * CENTS).into() + }])] + ); + }); + + ParaA::execute_with(|| { + let message = Xcm(vec![RequestUnlock { + asset: (Parent, 3 * CENTS).into(), + locker: Parent.into(), + }]); + + assert_ok!(ParachainPalletXcm::send_xcm(Here, (Parent, Parachain(2)), message.clone())); + }); + + Relay::execute_with(|| { + assert_eq!( + relay_chain::Balances::locks(¶chain_sovereign_account_id(1)), + vec![BalanceLock { id: *b"py/xcmlk", amount: 2 * CENTS, reasons: Reasons::All }] + ); + }); + } + + /// Scenario: + /// Parachain A sets two locks with Parachain B and Parachain C as unlockers on the relay chain. + /// Parachain A then requests Parachain B to partly unlock. + /// Note: The locks overlap. + /// Steps: + /// 1) Set locks on the relay chain. + /// Unlockers: B, C; Funds registered in pallet-xcm: 10, 5. + /// Lock set in pallet-balances: 10. + /// 2) Parachain B and C receive `NoteUnlockable` instruction. + /// 3) Parachain A sends an `RequestUnlock` instruction to Parachain B for 8 Cents. + /// 4) Parachain B Unlocks a part of the funds by sending a `UnlockAsset` instruction to the relay chain. + /// Unlockers: B, C; Funds registered in pallet-xcm: 2, 5. + /// Lock set in pallet-balances: 5. + /// + #[test] + fn locking_overlap() { + MockNet::reset(); + + // 1) + ParaA::execute_with(|| { + let message = Xcm(vec![ + LockAsset { asset: (Here, 10 * CENTS).into(), unlocker: (Parachain(2)).into() }, + LockAsset { asset: (Here, 5 * CENTS).into(), unlocker: (Parachain(3)).into() }, + ]); + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); + }); + + Relay::execute_with(|| { + assert_eq!( + relay_chain::Balances::locks(¶chain_sovereign_account_id(1)), + vec![BalanceLock { id: *b"py/xcmlk", amount: 10 * CENTS, reasons: Reasons::All }] + ); + }); + + // 2) + ParaB::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![NoteUnlockable { + owner: (Parent, Parachain(1)).into(), + asset: (Parent, 10 * CENTS).into() + }])] + ); + }); + + ParaC::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![NoteUnlockable { + owner: (Parent, Parachain(1)).into(), + asset: (Parent, 5 * CENTS).into() + }])] + ); + }); + + // 3) + ParaA::execute_with(|| { + let message = Xcm(vec![RequestUnlock { + asset: (Parent, 8 * CENTS).into(), + locker: Parent.into(), + }]); + + assert_ok!(ParachainPalletXcm::send_xcm(Here, (Parent, Parachain(2)), message.clone())); + }); + + // 4) + Relay::execute_with(|| { + assert_eq!( + relay_chain::Balances::locks(¶chain_sovereign_account_id(1)), + vec![BalanceLock { id: *b"py/xcmlk", amount: 5 * CENTS, reasons: Reasons::All }] + ); + }); + } +} diff --git a/examples/src/origins/mod.rs b/examples/src/origins/mod.rs new file mode 100644 index 0000000..97a775f --- /dev/null +++ b/examples/src/origins/mod.rs @@ -0,0 +1,37 @@ +#[cfg(test)] +mod tests { + use crate::simple_test_net::*; + use frame_support::{assert_ok, pallet_prelude::Weight}; + use xcm::latest::prelude::*; + use xcm_simulator::TestExt; + + const AMOUNT: u128 = 10; + const QUERY_ID: u64 = 1234; + + /// Scenario: + #[test] + fn descend_origin() { + MockNet::reset(); + ParaA::execute_with(|| { + let message = Xcm(vec![ + WithdrawAsset((Here, AMOUNT).into()), + BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + // Set the instructions that are executed when ExpectOrigin does not pass. + // In this case, reporting back an error to the Parachain. + SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), + })])), + DescendOrigin((PalletInstance(1)).into()), + // Checks if the XcmContext origin descended to `Parachain(1)/PalletInstance(1)`. + ExpectOrigin(Some((Parachain(1), PalletInstance(1)).into())), + ]); + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); + }); + + // Check that message queue is empty. + // The ExpectOrigin instruction passed so we should not receive an error response. + ParaA::execute_with(|| assert_eq!(parachain::MsgQueue::received_dmp(), vec![])); + } +} diff --git a/examples/src/queries/mod.rs b/examples/src/queries/mod.rs new file mode 100644 index 0000000..96004df --- /dev/null +++ b/examples/src/queries/mod.rs @@ -0,0 +1,194 @@ +#[cfg(test)] +mod tests { + use crate::simple_test_net::*; + use bounded_collections::BoundedVec; + use codec::Encode; + use frame_support::{assert_ok, pallet_prelude::Weight}; + use xcm::latest::prelude::*; + use xcm_simulator::TestExt; + + const AMOUNT: u128 = 1 * CENTS; + /// Arbitrary query id + const QUERY_ID: u64 = 1234; + + /// Scenario: + /// A parachain wants to be notified that a transfer worked correctly. + /// It sends a `ReportHolding` after the deposit to get notified on success. + /// + /// Asserts that the balances are updated correctly and the expected XCM is sent. + #[test] + fn query_holding() { + MockNet::reset(); + + // Send a message which fully succeeds to the relay chain. + // And then report the status of the holding register back to ParaA + ParaA::execute_with(|| { + let message = Xcm(vec![ + WithdrawAsset((Here, AMOUNT).into()), + BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: Unlimited }, + DepositAsset { + assets: Definite((Here, AMOUNT - 5).into()), + beneficiary: Parachain(2).into(), + }, + ReportHolding { + response_info: QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), + }, + assets: All.into(), + }, + ]); + + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); + }); + + // Check that transfer was executed + Relay::execute_with(|| { + // Withdraw executed + assert_eq!( + relay_chain::Balances::free_balance(parachain_sovereign_account_id(1)), + INITIAL_BALANCE - AMOUNT + ); + // Deposit executed + assert_eq!( + relay_chain::Balances::free_balance(parachain_sovereign_account_id(2)), + INITIAL_BALANCE + AMOUNT - 5 + ); + }); + + // Check that QueryResponse message was received + ParaA::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![QueryResponse { + query_id: QUERY_ID, + response: Response::Assets((Parent, AMOUNT - (AMOUNT - 5)).into()), + max_weight: Weight::from_all(0), + querier: Some(Here.into()), + }])], + ); + }); + } + + /// Scenario: + /// Parachain A wants to query the `PalletInfo` of the balances pallet in the relay chain. + /// It sends a `QueryPallet` instruction to the relay chain. + /// The relay chain responds with a `QueryResponse` instruction containing the `PalletInfo`. + /// + /// Asserts that the relay chain has the balances pallet configured. + #[test] + fn query_pallet() { + MockNet::reset(); + + ParaA::execute_with(|| { + let message = Xcm(vec![QueryPallet { + module_name: "pallet_balances".into(), + response_info: QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), + }, + }]); + + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); + print_para_events(); + }); + + ParaA::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![QueryResponse { + query_id: QUERY_ID, + response: Response::PalletsInfo(BoundedVec::truncate_from(vec![ + PalletInfo::new(1, "Balances".into(), "pallet_balances".into(), 4, 0, 0) + .unwrap() + ])), + max_weight: Weight::from_all(0), + querier: Some(Here.into()), + }])], + ); + }) + } + + /// Scenario: + /// Parachain A wants to know if the execution of their message on the relay chain succeeded without errors. + /// They set the ErrorHandler to report the value of the error register. + /// If the execution of the xcm instructions errors on the relay chain, the error is reported back to the Parachain. + /// + /// The Relay chain errors on the Trap instruction (Trap always throws an error). + #[test] + fn report_error() { + MockNet::reset(); + + let message = Xcm(vec![ + // Set the Error Handler to report back status of Error register. + SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), + })])), + Trap(1u64), + ]); + + ParaA::execute_with(|| { + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); + }); + + ParaA::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![QueryResponse { + query_id: QUERY_ID, + response: Response::ExecutionResult(Some((1, XcmError::Trap(1)))), + max_weight: Weight::from_all(0), + querier: Some(Here.into()), + }])], + ); + }); + } + + /// Scenario: + /// Parachain A wants to know if the execution of their `Transact` instruction on the relay chain succeeded without errors. + /// They add the `ReportTransactStatus` instruction to the XCM to get the status of the transact status register reported back. + #[test] + fn report_transact_status() { + MockNet::reset(); + + // Runtime call dispatched by the Transact instruction + let call = relay_chain::RuntimeCall::System( + frame_system::Call::::remark_with_event { + remark: "Hallo Relay!".as_bytes().to_vec(), + }, + ); + + let message = Xcm(vec![ + Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + call: call.encode().into(), + }, + ReportTransactStatus(QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), + }), + ]); + + ParaA::execute_with(|| { + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); + }); + + ParaA::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![QueryResponse { + query_id: QUERY_ID, + response: Response::DispatchResult(MaybeErrorCode::Success), + max_weight: Weight::from_all(0), + querier: Some(Here.into()), + }])], + ); + }); + } +} diff --git a/examples/src/simple_test_net/asset_hub.rs b/examples/src/simple_test_net/asset_hub.rs new file mode 100644 index 0000000..0937fc7 --- /dev/null +++ b/examples/src/simple_test_net/asset_hub.rs @@ -0,0 +1,530 @@ +// Copyright 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 . + +//! Asset hub parachain runtime mock. + +use super::{Balance, ForeignChainAliasAccount, UNITS}; +use codec::{Decode, Encode}; +use core::marker::PhantomData; +use frame_support::{ + construct_runtime, parameter_types, + traits::{ + AsEnsureOriginWithArg, ContainsPair, EnsureOrigin, EnsureOriginWithArg, Everything, + EverythingBut, Nothing, + }, + weights::{ + constants::{WEIGHT_PROOF_SIZE_PER_MB, WEIGHT_REF_TIME_PER_SECOND}, + Weight, + }, +}; +use frame_system::{EnsureRoot, EnsureSigned}; +use pallet_xcm::XcmPassthrough; +use polkadot_parachain::primitives::{ + DmpMessageHandler, Id as ParaId, Sibling, XcmpMessageFormat, XcmpMessageHandler, +}; +use polkadot_primitives::BlockNumber as RelayBlockNumber; +use sp_core::{ConstU128, ConstU32, H256}; +use sp_runtime::{ + testing::Header, + traits::{Get, Hash, IdentityLookup}, + AccountId32, +}; +use sp_std::prelude::*; +use xcm::{latest::prelude::*, VersionedXcm}; +use xcm_builder::{ + AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, Case, + ConvertedConcreteId, CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, + FixedRateOfFungible, FixedWeightBounds, FungiblesAdapter, IsConcrete, NativeAsset, NoChecking, + NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, +}; +use xcm_executor::{ + traits::{Convert, JustTry}, + Config, XcmExecutor, +}; + +pub type AccountId = AccountId32; +pub type AssetIdForAssets = u128; + +pub type SovereignAccountOf = ( + ForeignChainAliasAccount, + SiblingParachainConvertsVia, + AccountId32Aliases, + ParentIsPreset, +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +parameter_types! { + pub const AssetDeposit: u128 = 1_000_000; + pub const MetadataDepositBase: u128 = 1_000_000; + pub const MetadataDepositPerByte: u128 = 100_000; + pub const AssetAccountDeposit: u128 = 1_000_000; + pub const ApprovalDeposit: u128 = 1_000_000; + pub const AssetsStringLimit: u32 = 50; + pub const RemoveItemsLimit: u32 = 50; +} + +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = AssetIdForAssets; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = EnsureRoot; + type AssetDeposit = AssetDeposit; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type AssetAccountDeposit = AssetAccountDeposit; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = AssetsStringLimit; + type Freezer = (); + type Extra = (); + type WeightInfo = (); + type RemoveItemsLimit = RemoveItemsLimit; + type AssetIdParameter = AssetIdForAssets; + type CallbackHandle = (); +} + +// `EnsureOriginWithArg` impl for `CreateOrigin` which allows only XCM origins +// which are the correct sovereign account. +pub struct ForeignCreators; +impl EnsureOriginWithArg for ForeignCreators { + type Success = AccountId; + + fn try_origin( + o: RuntimeOrigin, + a: &MultiLocation, + ) -> sp_std::result::Result { + let origin_location = pallet_xcm::EnsureXcm::::try_origin(o.clone())?; + if !a.starts_with(&origin_location) { + return Err(o) + } + SovereignAccountOf::convert(origin_location).map_err(|_| o) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(a: &MultiLocation) -> Result { + Ok(pallet_xcm::Origin::Xcm(a.clone()).into()) + } +} + +impl pallet_uniques::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type CollectionId = u32; + type ItemId = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = EnsureRoot; + type CollectionDeposit = ConstU128<1_000>; + type ItemDeposit = ConstU128<1_000>; + type MetadataDepositBase = ConstU128<1_000>; + type AttributeDepositBase = ConstU128<1_000>; + type DepositPerByte = ConstU128<1>; + type StringLimit = ConstU32<64>; + type KeyLimit = ConstU32<64>; + type ValueLimit = ConstU32<128>; + type Locker = (); + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0); + pub const ReservedDmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0); +} + +parameter_types! { + pub const KsmLocation: MultiLocation = MultiLocation::parent(); + pub const TokenLocation: MultiLocation = Here.into_location(); + pub const RelayNetwork: NetworkId = ByGenesis([0; 32]); + pub UniversalLocation: InteriorMultiLocation = Parachain(MsgQueue::parachain_id().into()).into(); +} + +pub type XcmOriginToCallOrigin = ( + SovereignSignedViaLocation, + ParentAsSuperuser, + SignedAccountId32AsNative, + XcmPassthrough, +); + +parameter_types! { + pub const XcmInstructionWeight: Weight = Weight::from_parts(1_000, 1_000); + pub TokensPerSecondPerMegabyte: (AssetId, u128, u128) = (Concrete(Parent.into()), 1_000_000_000_000, 1024 * 1024); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; + pub ForeignPrefix: MultiLocation = (Parent,).into(); + pub CheckingAccount: AccountId = PolkadotXcm::check_account(); + pub TrustedLockPairs: (MultiLocation, MultiAssetFilter) = + (Parent.into(), Wild(AllOf { id: Concrete(Parent.into()), fun: WildFungible })); +} + +pub fn estimate_message_fee(number_of_instructions: u64) -> u128 { + let weight = estimate_message_weight(number_of_instructions); + + estimate_fee_for_weight(weight) +} + +pub fn estimate_message_weight(number_of_instructions: u64) -> Weight { + XcmInstructionWeight::get().saturating_mul(number_of_instructions) +} + +pub fn estimate_fee_for_weight(weight: Weight) -> u128 { + let (_, units_per_second, units_per_mb) = TokensPerSecondPerMegabyte::get(); + + units_per_second * (weight.ref_time() as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128) + + units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128) +} + +pub type LocalBalancesTransactor = + XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; + +pub struct FromMultiLocationToAsset( + core::marker::PhantomData<(MultiLocation, AssetId)>, +); +impl Convert + for FromMultiLocationToAsset +{ + fn convert(value: MultiLocation) -> Result { + match value { + MultiLocation { parents: 1, interior: Here } => Ok(0 as AssetIdForAssets), + MultiLocation { parents: 1, interior: X1(Parachain(para_id)) } => + Ok(para_id as AssetIdForAssets), + _ => Err(value), + } + } +} + +pub type AssetsTransactor = FungiblesAdapter< + Assets, + ConvertedConcreteId< + AssetIdForAssets, + Balance, + FromMultiLocationToAsset, + JustTry, + >, + SovereignAccountOf, + AccountId, + NoChecking, + CheckingAccount, +>; + +pub type ForeignUniquesTransactor = NonFungiblesAdapter< + ForeignUniques, + ConvertedConcreteId, JustTry>, + SovereignAccountOf, + AccountId, + NoChecking, + (), +>; + +/// Means for transacting assets on this chain +pub type AssetTransactors = (LocalBalancesTransactor, AssetsTransactor, ForeignUniquesTransactor); + +pub type XcmRouter = super::ParachainXcmRouter; +pub type Barrier = AllowTopLevelPaidExecutionFrom; + +parameter_types! { + pub NftCollectionOne: MultiAssetFilter + = Wild(AllOf { fun: WildNonFungible, id: Concrete((Parent, GeneralIndex(1)).into()) }); + pub NftCollectionOneForRelay: (MultiAssetFilter, MultiLocation) + = (NftCollectionOne::get(), Parent.into()); + pub RelayNativeAsset: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete((Parent, Here).into()) }); + pub RelayNativeAssetForRelay: (MultiAssetFilter, MultiLocation) = (RelayNativeAsset::get(), Parent.into()); +} +pub type TrustedTeleporters = + (xcm_builder::Case, xcm_builder::Case); +pub type TrustedReserves = EverythingBut>; + +pub struct XcmConfig; +impl Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = AssetTransactors; + type OriginConverter = XcmOriginToCallOrigin; + 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 = PolkadotXcm; + type AssetExchanger = (); + type AssetClaims = (); + type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = AllPalletsWithSystem; + type FeeManager = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; +} + +#[frame_support::pallet] +pub mod mock_msg_queue { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type XcmExecutor: ExecuteXcm; + } + + #[pallet::call] + impl Pallet {} + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::storage] + #[pallet::getter(fn parachain_id)] + pub(super) type ParachainId = StorageValue<_, ParaId, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn received_dmp)] + /// A queue of received DMP messages + pub(super) type ReceivedDmp = StorageValue<_, Vec>, ValueQuery>; + + impl Get for Pallet { + fn get() -> ParaId { + Self::parachain_id() + } + } + + pub type MessageId = [u8; 32]; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + // XCMP + /// Some XCM was executed OK. + Success(Option), + /// Some XCM failed. + Fail(Option, XcmError), + /// Bad XCM version used. + BadVersion(Option), + /// Bad XCM format used. + BadFormat(Option), + + // DMP + /// Downward message is invalid XCM. + InvalidFormat(MessageId), + /// Downward message is unsupported version of XCM. + UnsupportedVersion(MessageId), + /// Downward message executed with the given outcome. + ExecutedDownward(MessageId, Outcome), + } + + impl Pallet { + pub fn set_para_id(para_id: ParaId) { + ParachainId::::put(para_id); + } + + fn handle_xcmp_message( + sender: ParaId, + _sent_at: RelayBlockNumber, + xcm: VersionedXcm, + max_weight: Weight, + ) -> Result { + let hash = Encode::using_encoded(&xcm, T::Hashing::hash); + let message_hash = Encode::using_encoded(&xcm, sp_io::hashing::blake2_256); + let (result, event) = match Xcm::::try_from(xcm) { + Ok(xcm) => { + 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 + // we just report the weight used. + Outcome::Incomplete(w, e) => (Ok(w), Event::Fail(Some(hash), e)), + } + }, + Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))), + }; + Self::deposit_event(event); + result + } + } + + impl XcmpMessageHandler for Pallet { + fn handle_xcmp_messages<'a, I: Iterator>( + iter: I, + max_weight: Weight, + ) -> Weight { + for (sender, sent_at, data) in iter { + let mut data_ref = data; + let _ = XcmpMessageFormat::decode(&mut data_ref) + .expect("Simulator encodes with versioned xcm format; qed"); + + let mut remaining_fragments = &data_ref[..]; + while !remaining_fragments.is_empty() { + if let Ok(xcm) = + VersionedXcm::::decode(&mut remaining_fragments) + { + let _ = Self::handle_xcmp_message(sender, sent_at, xcm, max_weight); + } else { + debug_assert!(false, "Invalid incoming XCMP message data"); + } + } + } + max_weight + } + } + + impl DmpMessageHandler for Pallet { + fn handle_dmp_messages( + iter: impl Iterator)>, + limit: Weight, + ) -> Weight { + for (_i, (_sent_at, data)) in iter.enumerate() { + let id = sp_io::hashing::blake2_256(&data[..]); + let maybe_versioned = VersionedXcm::::decode(&mut &data[..]); + match maybe_versioned { + Err(_) => { + Self::deposit_event(Event::InvalidFormat(id)); + }, + 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)); + }, + }, + } + } + limit + } + } +} + +impl mock_msg_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Parent.into()); +} + +pub struct TrustedLockerCase(PhantomData); +impl> ContainsPair + for TrustedLockerCase +{ + fn contains(origin: &MultiLocation, asset: &MultiAsset) -> bool { + let (o, a) = T::get(); + a.matches(asset) && &o == origin + } +} + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Nothing; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = IsConcrete; + type TrustedLockers = TrustedLockerCase; + type SovereignAccountOf = SovereignAccountOf; + type MaxLockers = ConstU32<8>; + type WeightInfo = pallet_xcm::TestWeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + MsgQueue: mock_msg_queue::{Pallet, Storage, Event}, + PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin}, + Assets: pallet_assets, + ForeignUniques: pallet_uniques, + } +); diff --git a/examples/src/simple_test_net/mod.rs b/examples/src/simple_test_net/mod.rs new file mode 100644 index 0000000..8dc24b8 --- /dev/null +++ b/examples/src/simple_test_net/mod.rs @@ -0,0 +1,158 @@ +// 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 parachain; +pub mod relay_chain; + +use frame_support::{assert_ok, sp_tracing, traits::GenesisBuild}; +use xcm::prelude::*; +use xcm_executor::traits::Convert; +use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; + +// Accounts +pub const ADMIN: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0u8; 32]); +pub const ALICE: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([1u8; 32]); +pub const BOB: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([2u8; 32]); + +// Balances +pub type Balance = u128; +pub const UNITS: Balance = 10_000_000_000; +pub const CENTS: Balance = UNITS / 100; // 100_000_000 +pub const INITIAL_BALANCE: u128 = 1 * UNITS; + +decl_test_parachain! { + pub struct ParaA { + Runtime = parachain::Runtime, + XcmpMessageHandler = parachain::MsgQueue, + DmpMessageHandler = parachain::MsgQueue, + new_ext = para_ext(1), + } +} + +decl_test_parachain! { + pub struct ParaB { + Runtime = parachain::Runtime, + XcmpMessageHandler = parachain::MsgQueue, + DmpMessageHandler = parachain::MsgQueue, + new_ext = para_ext(2), + } +} + +decl_test_parachain! { + pub struct ParaC { + Runtime = parachain::Runtime, + XcmpMessageHandler = parachain::MsgQueue, + DmpMessageHandler = parachain::MsgQueue, + new_ext = para_ext(3), + } +} + +decl_test_relay_chain! { + pub struct Relay { + Runtime = relay_chain::Runtime, + XcmConfig = relay_chain::XcmConfig, + new_ext = relay_ext(), + } +} + +decl_test_network! { + pub struct MockNet { + relay_chain = Relay, + parachains = vec![ + (1, ParaA), + (2, ParaB), + (3, ParaC), + ], + } +} + +pub fn relay_sovereign_account_id() -> parachain::AccountId { + let location = (Parent,); + parachain::SovereignAccountOf::convert(location.into()).unwrap() +} + +pub fn parachain_sovereign_account_id(para: u32) -> relay_chain::AccountId { + let location = (Parachain(para),); + relay_chain::SovereignAccountOf::convert(location.into()).unwrap() +} + +pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { + use parachain::{MsgQueue, Runtime, System}; + + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![(ALICE, INITIAL_BALANCE), (relay_sovereign_account_id(), INITIAL_BALANCE)], + } + .assimilate_storage(&mut t) + .unwrap(); + + pallet_assets::GenesisConfig:: { + assets: vec![ + (1u128, ADMIN, false, 1u128), // Create derivative asset for relay's native token + ], + metadata: Default::default(), + accounts: vec![(1u128, ALICE, 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()); + }); + ext +} + +pub fn relay_ext() -> sp_io::TestExternalities { + use relay_chain::{Runtime, System}; + + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (ALICE, INITIAL_BALANCE), + (parachain_sovereign_account_id(1), INITIAL_BALANCE), + (parachain_sovereign_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 +} + +pub fn print_para_events() { + use parachain::System; + System::events().iter().for_each(|r| println!(">>> {:?}", r.event)); +} + +pub fn print_relay_events() { + use relay_chain::System; + System::events().iter().for_each(|r| println!(">>> {:?}", r.event)); +} + +pub type RelaychainPalletXcm = pallet_xcm::Pallet; +pub type ParachainPalletXcm = pallet_xcm::Pallet; +pub type RelaychainBalances = pallet_balances::Pallet; +pub type ParachainBalances = pallet_balances::Pallet; +pub type ParachainAssets = pallet_assets::Pallet; diff --git a/examples/src/simple_test_net/parachain.rs b/examples/src/simple_test_net/parachain.rs new file mode 100644 index 0000000..8ec22c2 --- /dev/null +++ b/examples/src/simple_test_net/parachain.rs @@ -0,0 +1,543 @@ +// 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 . + +//! Parachain runtime mock. + +use super::Balance; +use codec::{Decode, Encode}; +use core::marker::PhantomData; +use frame_support::{ + construct_runtime, ensure, parameter_types, + traits::{ + AsEnsureOriginWithArg, ContainsPair, EnsureOrigin, EnsureOriginWithArg, Everything, + EverythingBut, Nothing, + }, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, +}; +use frame_system::{EnsureRoot, EnsureSigned}; +use pallet_xcm::XcmPassthrough; +use polkadot_parachain::primitives::{ + DmpMessageHandler, Id as ParaId, Sibling, XcmpMessageFormat, XcmpMessageHandler, +}; +use polkadot_primitives::BlockNumber as RelayBlockNumber; +use sp_core::{ConstU128, ConstU32, H256}; +use sp_runtime::{ + testing::Header, + traits::{Get, Hash, IdentityLookup}, + AccountId32, +}; + +use sp_std::{cell::RefCell, prelude::*}; +use xcm::{latest::prelude::*, VersionedXcm}; +use xcm_builder::{ + AccountId32Aliases, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, Case, + ConvertedConcreteId, CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, + FixedRateOfFungible, FixedWeightBounds, FungiblesAdapter, IsConcrete, NativeAsset, NoChecking, + NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, +}; +use xcm_executor::{ + traits::{AssetExchange, Convert, JustTry}, + Assets as HoldingAssets, Config, XcmExecutor, +}; + +pub type AccountId = AccountId32; +pub type AssetIdForAssets = u128; + +pub type SovereignAccountOf = ( + SiblingParachainConvertsVia, + AccountId32Aliases, + ParentIsPreset, +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +parameter_types! { + pub const AssetDeposit: u128 = 1_000_000; + pub const MetadataDepositBase: u128 = 1_000_000; + pub const MetadataDepositPerByte: u128 = 100_000; + pub const AssetAccountDeposit: u128 = 1_000_000; + pub const ApprovalDeposit: u128 = 1_000_000; + pub const AssetsStringLimit: u32 = 50; + pub const RemoveItemsLimit: u32 = 50; +} + +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = AssetIdForAssets; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = EnsureRoot; + type AssetDeposit = AssetDeposit; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type AssetAccountDeposit = AssetAccountDeposit; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = AssetsStringLimit; + type Freezer = (); + type Extra = (); + type WeightInfo = (); + type RemoveItemsLimit = RemoveItemsLimit; + type AssetIdParameter = AssetIdForAssets; + type CallbackHandle = (); +} + +// `EnsureOriginWithArg` impl for `CreateOrigin` which allows only XCM origins +// which are the correct sovereign account. +pub struct ForeignCreators; +impl EnsureOriginWithArg for ForeignCreators { + type Success = AccountId; + + fn try_origin( + o: RuntimeOrigin, + a: &MultiLocation, + ) -> sp_std::result::Result { + let origin_location = pallet_xcm::EnsureXcm::::try_origin(o.clone())?; + if !a.starts_with(&origin_location) { + return Err(o) + } + SovereignAccountOf::convert(origin_location).map_err(|_| o) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(a: &MultiLocation) -> Result { + Ok(pallet_xcm::Origin::Xcm(a.clone()).into()) + } +} + +impl pallet_uniques::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type CollectionId = u32; + type ItemId = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = EnsureRoot; + type CollectionDeposit = ConstU128<1_000>; + type ItemDeposit = ConstU128<1_000>; + type MetadataDepositBase = ConstU128<1_000>; + type AttributeDepositBase = ConstU128<1_000>; + type DepositPerByte = ConstU128<1>; + type StringLimit = ConstU32<64>; + type KeyLimit = ConstU32<64>; + type ValueLimit = ConstU32<128>; + type Locker = (); + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0); + pub const ReservedDmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0); +} + +parameter_types! { + pub const KsmLocation: MultiLocation = MultiLocation::parent(); + pub const TokenLocation: MultiLocation = Here.into_location(); + pub const RelayNetwork: NetworkId = ByGenesis([0; 32]); + pub UniversalLocation: InteriorMultiLocation = Parachain(MsgQueue::parachain_id().into()).into(); +} + +pub type XcmOriginToCallOrigin = ( + SovereignSignedViaLocation, + ParentAsSuperuser, + SignedAccountId32AsNative, + XcmPassthrough, +); + +parameter_types! { + pub const UnitWeightCost: Weight = Weight::from_parts(1, 1); + pub RatePerSecondPerByte: (AssetId, u128, u128) = (Concrete(Parent.into()), 1, 1); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; + pub ForeignPrefix: MultiLocation = (Parent,).into(); + pub CheckingAccount: AccountId = PolkadotXcm::check_account(); + pub TrustedLockPairs: (MultiLocation, MultiAssetFilter) = + (Parent.into(), Wild(AllOf { id: Concrete(Parent.into()), fun: WildFungible })); +} + +pub type LocalBalancesTransactor = + XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; + +pub struct FromNativeAssetToFungible( + core::marker::PhantomData<(MultiLocation, AssetId)>, +); +impl Convert + for FromNativeAssetToFungible +{ + fn convert(value: MultiLocation) -> Result { + match value { + MultiLocation { parents: 1, interior: Here } => Ok(1 as AssetIdForAssets), + _ => Err(value), + } + } +} + +pub type ForeignAssetsTransactor = FungiblesAdapter< + Assets, + ConvertedConcreteId< + AssetIdForAssets, + Balance, + FromNativeAssetToFungible, + JustTry, + >, + SovereignAccountOf, + AccountId, + NoChecking, + CheckingAccount, +>; + +pub type ForeignUniquesTransactor = NonFungiblesAdapter< + ForeignUniques, + ConvertedConcreteId, JustTry>, + SovereignAccountOf, + AccountId, + NoChecking, + (), +>; + +/// Means for transacting assets on this chain +pub type AssetTransactors = + (LocalBalancesTransactor, ForeignAssetsTransactor, ForeignUniquesTransactor); + +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 RelayNativeAsset: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete((Parent, Here).into()) }); + pub RelayNativeAssetForRelay: (MultiAssetFilter, MultiLocation) = (RelayNativeAsset::get(), Parent.into()); +} +pub type TrustedTeleporters = + (xcm_builder::Case, xcm_builder::Case); +pub type TrustedReserves = EverythingBut>; + +thread_local! { + pub static EXCHANGE_ASSETS: RefCell = RefCell::new(HoldingAssets::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()) +} +/// Simple implementation of AssetExchange. +/// If maximal is true we take all assets in the exchange +/// for the assets we want to give. +/// If maximal is false, we take exactly what we want in return for all assets in give. +pub struct TestAssetExchange; +impl AssetExchange for TestAssetExchange { + fn exchange_asset( + _origin: Option<&MultiLocation>, + give: HoldingAssets, + 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, HoldingAssets::new()) + } else { + have.saturating_take(want.clone().into()) + }; + have.subsume_assets(give); + EXCHANGE_ASSETS.with(|l| l.replace(have)); + Ok(get) + } +} + +pub struct XcmConfig; +impl Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = AssetTransactors; + type OriginConverter = XcmOriginToCallOrigin; + type IsReserve = (NativeAsset, TrustedReserves); + type IsTeleporter = TrustedTeleporters; + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfFungible; + type ResponseHandler = (); + type AssetTrap = PolkadotXcm; + type AssetLocker = PolkadotXcm; + type AssetExchanger = TestAssetExchange; + type AssetClaims = PolkadotXcm; + type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = AllPalletsWithSystem; + type FeeManager = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; +} + +#[frame_support::pallet] +pub mod mock_msg_queue { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type XcmExecutor: ExecuteXcm; + } + + #[pallet::call] + impl Pallet {} + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::storage] + #[pallet::getter(fn parachain_id)] + pub(super) type ParachainId = StorageValue<_, ParaId, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn received_dmp)] + /// A queue of received DMP messages + pub(super) type ReceivedDmp = StorageValue<_, Vec>, ValueQuery>; + + impl Get for Pallet { + fn get() -> ParaId { + Self::parachain_id() + } + } + + pub type MessageId = [u8; 32]; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + // XCMP + /// Some XCM was executed OK. + Success(Option), + /// Some XCM failed. + Fail(Option, XcmError), + /// Bad XCM version used. + BadVersion(Option), + /// Bad XCM format used. + BadFormat(Option), + + // DMP + /// Downward message is invalid XCM. + InvalidFormat(MessageId), + /// Downward message is unsupported version of XCM. + UnsupportedVersion(MessageId), + /// Downward message executed with the given outcome. + ExecutedDownward(MessageId, Outcome), + } + + impl Pallet { + pub fn set_para_id(para_id: ParaId) { + ParachainId::::put(para_id); + } + + fn handle_xcmp_message( + sender: ParaId, + _sent_at: RelayBlockNumber, + xcm: VersionedXcm, + max_weight: Weight, + ) -> Result { + let hash = Encode::using_encoded(&xcm, T::Hashing::hash); + let message_hash = Encode::using_encoded(&xcm, sp_io::hashing::blake2_256); + let (result, event) = match Xcm::::try_from(xcm) { + Ok(xcm) => { + 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 + // we just report the weight used. + Outcome::Incomplete(w, e) => (Ok(w), Event::Fail(Some(hash), e)), + } + }, + Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))), + }; + Self::deposit_event(event); + result + } + } + + impl XcmpMessageHandler for Pallet { + fn handle_xcmp_messages<'a, I: Iterator>( + iter: I, + max_weight: Weight, + ) -> Weight { + for (sender, sent_at, data) in iter { + let mut data_ref = data; + let _ = XcmpMessageFormat::decode(&mut data_ref) + .expect("Simulator encodes with versioned xcm format; qed"); + + let mut remaining_fragments = &data_ref[..]; + while !remaining_fragments.is_empty() { + if let Ok(xcm) = + VersionedXcm::::decode(&mut remaining_fragments) + { + let _ = Self::handle_xcmp_message(sender, sent_at, xcm, max_weight); + } else { + debug_assert!(false, "Invalid incoming XCMP message data"); + } + } + } + max_weight + } + } + + impl DmpMessageHandler for Pallet { + fn handle_dmp_messages( + iter: impl Iterator)>, + limit: Weight, + ) -> Weight { + for (_i, (_sent_at, data)) in iter.enumerate() { + let id = sp_io::hashing::blake2_256(&data[..]); + let maybe_versioned = VersionedXcm::::decode(&mut &data[..]); + match maybe_versioned { + Err(_) => { + Self::deposit_event(Event::InvalidFormat(id)); + }, + 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)); + }, + }, + } + } + limit + } + } +} + +impl mock_msg_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Parent.into()); +} + +pub struct TrustedLockerCase(PhantomData); +impl> ContainsPair + for TrustedLockerCase +{ + fn contains(origin: &MultiLocation, asset: &MultiAsset) -> bool { + let (o, a) = T::get(); + a.matches(asset) && &o == origin + } +} + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Nothing; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = IsConcrete; + type TrustedLockers = TrustedLockerCase; + type SovereignAccountOf = SovereignAccountOf; + type MaxLockers = ConstU32<8>; + type WeightInfo = pallet_xcm::TestWeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + MsgQueue: mock_msg_queue::{Pallet, Storage, Event}, + PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin}, + Assets: pallet_assets, + ForeignUniques: pallet_uniques, + } +); diff --git a/examples/src/simple_test_net/relay_chain.rs b/examples/src/simple_test_net/relay_chain.rs new file mode 100644 index 0000000..84552c6 --- /dev/null +++ b/examples/src/simple_test_net/relay_chain.rs @@ -0,0 +1,256 @@ +// 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 . + +//! Relay chain runtime mock. + +use frame_support::{ + construct_runtime, parameter_types, + traits::{AsEnsureOriginWithArg, Everything, Nothing}, + weights::Weight, +}; + +use frame_system::EnsureRoot; +use sp_core::{ConstU128, ConstU32, H256}; +use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; + +use polkadot_parachain::primitives::Id as ParaId; +use polkadot_runtime_parachains::{configuration, origin, shared, ump}; +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, ChildParachainAsNative, + ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, ConvertedConcreteId, + CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete, + NoChecking, NonFungiblesAdapter, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, +}; +use xcm_executor::{traits::JustTry, Config, XcmExecutor}; + +use super::Balance; + +pub type AccountId = AccountId32; + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +impl pallet_uniques::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type CollectionId = u32; + type ItemId = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = EnsureRoot; + type CollectionDeposit = ConstU128<1_000>; + type ItemDeposit = ConstU128<1_000>; + type MetadataDepositBase = ConstU128<1_000>; + type AttributeDepositBase = ConstU128<1_000>; + type DepositPerByte = ConstU128<1>; + type StringLimit = ConstU32<64>; + type KeyLimit = ConstU32<64>; + type ValueLimit = ConstU32<128>; + type Locker = (); + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); +} + +impl shared::Config for Runtime {} + +impl configuration::Config for Runtime { + type WeightInfo = configuration::TestWeightInfo; +} + +parameter_types! { + pub RelayNetwork: NetworkId = ByGenesis([0; 32]); + pub const TokenLocation: MultiLocation = Here.into_location(); + pub UniversalLocation: InteriorMultiLocation = Here; + pub UnitWeightCost: u64 = 1_000; +} + +pub type SovereignAccountOf = ( + AccountId32Aliases, + ChildParachainConvertsVia, + SiblingParachainConvertsVia, +); + +pub type LocalBalancesTransactor = + XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; + +pub type LocalUniquesTransactor = NonFungiblesAdapter< + Uniques, + ConvertedConcreteId, JustTry>, + SovereignAccountOf, + AccountId, + NoChecking, + (), +>; + +pub type AssetTransactors = (LocalBalancesTransactor, LocalUniquesTransactor); + +type LocalOriginConverter = ( + SovereignSignedViaLocation, + ChildParachainAsNative, + SignedAccountId32AsNative, + ChildSystemParachainAsSuperuser, +); + +parameter_types! { + pub const BaseXcmWeight: Weight = Weight::from_parts(1_000, 1_000); + pub TokensPerSecondPerByte: (AssetId, u128, u128) = + (Concrete(TokenLocation::get()), 1_000_000_000_000, 1024 * 1024); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; +} + +pub type XcmRouter = super::RelayChainXcmRouter; +pub type Barrier = AllowUnpaidExecutionFrom; + +pub struct XcmConfig; +impl Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = AssetTransactors; + type OriginConverter = LocalOriginConverter; + type IsReserve = (); + type IsTeleporter = (); + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfFungible; + type ResponseHandler = XcmPallet; + type AssetTrap = XcmPallet; + type AssetLocker = XcmPallet; + type AssetExchanger = (); + type AssetClaims = XcmPallet; + type SubscriptionService = XcmPallet; + type PalletInstancesInfo = AllPalletsWithSystem; + type FeeManager = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Parachain(1).into()); +} + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmRouter = XcmRouter; + // Anyone can execute XCM messages locally... + type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = IsConcrete; + type TrustedLockers = (); + type SovereignAccountOf = SovereignAccountOf; + type MaxLockers = ConstU32<8>; + type WeightInfo = pallet_xcm::TestWeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; +} + +parameter_types! { + pub const FirstMessageFactorPercent: u64 = 100; +} + +impl ump::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type UmpSink = ump::XcmSink, Runtime>; + type FirstMessageFactorPercent = FirstMessageFactorPercent; + type ExecuteOverweightOrigin = frame_system::EnsureRoot; + type WeightInfo = ump::TestWeightInfo; +} + +impl origin::Config for Runtime {} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Uniques: pallet_uniques, + ParasOrigin: origin::{Pallet, Origin}, + ParasUmp: ump::{Pallet, Call, Storage, Event}, + XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin}, + } +); diff --git a/examples/src/transact/mod.rs b/examples/src/transact/mod.rs new file mode 100644 index 0000000..b1b9841 --- /dev/null +++ b/examples/src/transact/mod.rs @@ -0,0 +1,95 @@ +#[cfg(test)] +mod tests { + use crate::simple_test_net::*; + use codec::Encode; + use frame_support::{assert_ok, pallet_prelude::Weight}; + use xcm::latest::prelude::*; + use xcm_simulator::TestExt; + + const AMOUNT: u128 = 1 * CENTS; + + /// Scenario: + /// Relay chain sets the balance of Alice on Parachain(1). + /// The relay chain is able to do this, because Parachain(1) trusts the relay chain to execute runtime calls as root. + #[test] + fn transact_set_balance() { + MockNet::reset(); + // Runtime call dispatched by the Transact instruction. + // set_balance requires root origin. + let call = parachain::RuntimeCall::Balances( + pallet_balances::Call::::set_balance { + who: ALICE, + new_free: 5 * AMOUNT, + new_reserved: 0, + }, + ); + + let message = Xcm(vec![ + WithdrawAsset((Here, AMOUNT).into()), + BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + Transact { + origin_kind: OriginKind::Superuser, + require_weight_at_most: Weight::from_parts(1_000_000_000, 1024 * 1024), + call: call.encode().into(), + }, + ]); + + Relay::execute_with(|| { + assert_ok!(RelaychainPalletXcm::send_xcm(Here, Parachain(1), message.clone(),)); + }); + + ParaA::execute_with(|| { + assert_eq!(ParachainBalances::free_balance(ALICE), 5 * AMOUNT); + }) + } + + /// Scenario: + /// Parachain A sends two transact instructions to the relay chain. + /// The first instruction creates a NFT collection with as admin Parachain A. + /// The second instruction mints a NFT for the collection with as Owner ALICE. + #[test] + fn transact_mint_nft() { + MockNet::reset(); + let create_collection = relay_chain::RuntimeCall::Uniques(pallet_uniques::Call::< + relay_chain::Runtime, + >::create { + collection: 1u32, + admin: parachain_sovereign_account_id(1), + }); + + let mint = + relay_chain::RuntimeCall::Uniques(pallet_uniques::Call::::mint { + collection: 1u32, + item: 1u32, + owner: ALICE, + }); + + let message = Xcm(vec![ + WithdrawAsset((Here, AMOUNT).into()), + BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + call: create_collection.encode().into(), + }, + Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + call: mint.encode().into(), + }, + ]); + + // Create collection with Alice as owner. + ParaA::execute_with(|| { + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); + }); + + Relay::execute_with(|| { + assert_eq!( + relay_chain::Uniques::collection_owner(1u32), + Some(parachain_sovereign_account_id(1)) + ); + assert_eq!(relay_chain::Uniques::owner(1u32, 1u32), Some(ALICE)); + }); + } +} diff --git a/examples/src/transfers/mod.rs b/examples/src/transfers/mod.rs new file mode 100644 index 0000000..ff5e7ce --- /dev/null +++ b/examples/src/transfers/mod.rs @@ -0,0 +1,2 @@ +mod reserve; +mod teleport; diff --git a/examples/src/transfers/reserve.rs b/examples/src/transfers/reserve.rs new file mode 100644 index 0000000..36bd7ac --- /dev/null +++ b/examples/src/transfers/reserve.rs @@ -0,0 +1,108 @@ +#[cfg(test)] +mod tests { + use crate::simple_test_net::*; + use frame_support::assert_ok; + use xcm::latest::prelude::*; + use xcm_simulator::TestExt; + + /// Scenario: + /// ALICE transfers her FDOT from parachain A to parachain B. + #[test] + fn reserve_backed_transfer_para_to_para() { + MockNet::reset(); + + let withdraw_amount = 50 * CENTS; + + let message: Xcm = Xcm(vec![ + WithdrawAsset((Parent, withdraw_amount).into()), + InitiateReserveWithdraw { + assets: All.into(), + reserve: Parent.into(), + xcm: Xcm(vec![ + BuyExecution { + fees: (Here, withdraw_amount).into(), + weight_limit: WeightLimit::Unlimited, + }, + DepositReserveAsset { + assets: All.into(), + dest: Parachain(2).into(), + xcm: Xcm(vec![DepositAsset { + assets: All.into(), + beneficiary: Junction::AccountId32 { id: ALICE.into(), network: None } + .into(), + }]), + }, + ]), + }, + ]); + + ParaA::execute_with(|| { + assert_ok!(parachain::PolkadotXcm::execute( + parachain::RuntimeOrigin::signed(ALICE), + Box::new(xcm::VersionedXcm::V3(message.into())), + (100_000_000_000, 100_000_000_000).into(), + )); + + assert_eq!(parachain::Assets::balance(1, &ALICE), INITIAL_BALANCE - withdraw_amount); + }); + + Relay::execute_with(|| { + assert_eq!( + relay_chain::Balances::free_balance(¶chain_sovereign_account_id(2)), + INITIAL_BALANCE + withdraw_amount + ); + }); + + ParaB::execute_with(|| { + assert_eq!(parachain::Assets::balance(1, &ALICE), INITIAL_BALANCE + withdraw_amount); + }); + } + + /// Scenario: + /// ALICE transfers her FDOT from relay to parachain B. + #[test] + fn reserve_backed_transfer_relay_to_para() { + MockNet::reset(); + + let withdraw_amount = 50 * CENTS; + + let message: Xcm = Xcm(vec![TransferReserveAsset { + assets: (Here, withdraw_amount).into(), + dest: Parachain(2).into(), + xcm: Xcm(vec![ + BuyExecution { + fees: (Here, withdraw_amount).into(), + weight_limit: WeightLimit::Unlimited, + }, + DepositAsset { + assets: All.into(), + beneficiary: Junction::AccountId32 { id: ALICE.into(), network: None }.into(), + }, + ]), + }]); + + Relay::execute_with(|| { + assert_ok!(relay_chain::XcmPallet::execute( + relay_chain::RuntimeOrigin::signed(ALICE), + Box::new(xcm::VersionedXcm::V3(message.into())), + (100_000_000_000, 100_000_000_000).into(), + )); + + // ALICE's balance in the relay chain decreases + assert_eq!( + relay_chain::Balances::free_balance(&ALICE), + INITIAL_BALANCE - withdraw_amount + ); + + // Parachain(2)'s sovereign account's balance increases + assert_eq!( + relay_chain::Balances::free_balance(¶chain_sovereign_account_id(2)), + INITIAL_BALANCE + withdraw_amount + ); + }); + + ParaB::execute_with(|| { + assert_eq!(parachain::Assets::balance(1, &ALICE), INITIAL_BALANCE + withdraw_amount); + }); + } +} diff --git a/examples/src/transfers/teleport.rs b/examples/src/transfers/teleport.rs new file mode 100644 index 0000000..0cc18d0 --- /dev/null +++ b/examples/src/transfers/teleport.rs @@ -0,0 +1,125 @@ +#[cfg(test)] +mod tests { + use crate::simple_test_net::*; + use frame_support::assert_ok; + use xcm::latest::prelude::*; + use xcm_simulator::TestExt; + + /// Scenario: + /// ALICE teleports her native assets from the relay chain to parachain A. + #[test] + fn teleport_fungible() { + MockNet::reset(); + + let teleport_amount = 50 * CENTS; + let message: Xcm = Xcm(vec![ + WithdrawAsset((Here, teleport_amount).into()), + InitiateTeleport { + assets: AllCounted(1).into(), + dest: Parachain(1).into(), + xcm: Xcm(vec![DepositAsset { + assets: AllCounted(1).into(), + beneficiary: Junction::AccountId32 { network: None, id: ALICE.into() }.into(), + }]), + }, + ]); + + Relay::execute_with(|| { + assert_ok!(relay_chain::XcmPallet::execute( + relay_chain::RuntimeOrigin::signed(ALICE), + Box::new(xcm::VersionedXcm::V3(message.into())), + (100_000_000_000, 100_000_000_000).into() + )); + + assert_eq!( + relay_chain::Balances::free_balance(ALICE), + INITIAL_BALANCE - teleport_amount + ); + }); + + ParaA::execute_with(|| { + let expected_message_received: Xcm = Xcm(vec![ + ReceiveTeleportedAsset(vec![(Parent, teleport_amount).into()].into()), + ClearOrigin, + DepositAsset { + assets: AllCounted(1).into(), + beneficiary: Junction::AccountId32 { network: None, id: ALICE.into() }.into(), + }, + ]); + + assert_eq!(parachain::MsgQueue::received_dmp(), vec![expected_message_received]); + + assert_eq!(parachain::Assets::balance(1, &ALICE), INITIAL_BALANCE + teleport_amount); + }); + } + + /// Scenario: + /// ALICE teleports her nft from the relay chain to parachain A + #[test] + fn teleport_nft() { + MockNet::reset(); + + Relay::execute_with(|| { + // Mint NFT for Alice on Relay chain + assert_ok!(relay_chain::Uniques::force_create( + relay_chain::RuntimeOrigin::root(), + 1, + ALICE, + true + )); + assert_ok!(relay_chain::Uniques::mint( + relay_chain::RuntimeOrigin::signed(ALICE), + 1, + 42, + ALICE + )); + + assert_eq!(relay_chain::Uniques::owner(1, 42), Some(ALICE)); + }); + + ParaA::execute_with(|| { + // Create NFT collection representing the relay chain one + assert_ok!(parachain::ForeignUniques::force_create( + parachain::RuntimeOrigin::root(), + 1u32, + ALICE, + false + )); + + // Alice is Collection Owner. + assert_eq!(parachain::ForeignUniques::collection_owner(1u32), Some(ALICE)); + // Alice does not own Collection Item 42 yet. + assert_eq!(parachain::ForeignUniques::owner(1u32, 42u32.into()), None); + assert_eq!(parachain::Balances::reserved_balance(&ALICE), 0); + }); + + let message: Xcm = Xcm(vec![ + WithdrawAsset((GeneralIndex(1), 42u64).into()), + InitiateTeleport { + assets: AllCounted(1).into(), + dest: Parachain(1).into(), + xcm: Xcm(vec![DepositAsset { + assets: AllCounted(1).into(), + beneficiary: Junction::AccountId32 { id: ALICE.into(), network: None }.into(), + }]), + }, + ]); + + Relay::execute_with(|| { + assert_ok!(relay_chain::XcmPallet::execute( + relay_chain::RuntimeOrigin::signed(ALICE), + Box::new(xcm::VersionedXcm::V3(message.into())), + (100_000_000_000, 100_000_000_000).into(), + )); + }); + + ParaA::execute_with(|| { + assert_eq!(parachain::ForeignUniques::owner(1u32, 42u32.into()), Some(ALICE)); + assert_eq!(parachain::Balances::reserved_balance(&ALICE), 1000); + }); + + Relay::execute_with(|| { + assert_eq!(relay_chain::Uniques::owner(1, 42), None); + }); + } +} diff --git a/examples/src/trap_and_claim/mod.rs b/examples/src/trap_and_claim/mod.rs new file mode 100644 index 0000000..1651b00 --- /dev/null +++ b/examples/src/trap_and_claim/mod.rs @@ -0,0 +1,74 @@ +#[cfg(test)] +mod tests { + use crate::simple_test_net::*; + use frame_support::{assert_ok, pallet_prelude::Weight}; + use xcm::latest::prelude::*; + use xcm_simulator::TestExt; + + const QUERY_ID: u64 = 1234; + + /// Scenario: + /// Parachain A withdraws funds from its sovereign account on the relay chain. + /// The assets are trapped because an error is thrown and the execution is halted. + /// Parachain A claims the trapped assets and receives a report of the holding register. + /// It then deposits the assets in the account of ALICE. + #[test] + fn trap_and_claim_assets() { + let message = Xcm(vec![ + WithdrawAsset((Here, 10 * CENTS).into()), + BuyExecution { fees: (Here, CENTS).into(), weight_limit: WeightLimit::Unlimited }, + Trap(0), // <-- Errors + DepositAsset { + // <-- Not executed because of error. + assets: All.into(), + beneficiary: AccountId32 { + network: Some(parachain::RelayNetwork::get()), + id: ALICE.into(), + } + .into(), + }, + ]); + ParaA::execute_with(|| { + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); + }); + + let claim_message = Xcm(vec![ + ClaimAsset { assets: (Here, 10 * CENTS).into(), ticket: Here.into() }, + ReportHolding { + response_info: QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_parts(1_000_000_000, 64 * 64), + }, + assets: All.into(), + }, + DepositAsset { + assets: All.into(), + beneficiary: AccountId32 { + network: Some(parachain::RelayNetwork::get()), + id: ALICE.into(), + } + .into(), + }, + ]); + ParaA::execute_with(|| { + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, claim_message.clone())); + }); + + Relay::execute_with(|| { + assert_eq!(RelaychainBalances::free_balance(ALICE), INITIAL_BALANCE + 10 * CENTS); + }); + + ParaA::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![QueryResponse { + query_id: QUERY_ID, + response: Response::Assets((Parent, 10 * CENTS).into()), + max_weight: Weight::from_parts(1_000_000_000, 64 * 64), + querier: Some(Here.into()), + }])], + ) + }); + } +} diff --git a/examples/src/version_subscription/mod.rs b/examples/src/version_subscription/mod.rs new file mode 100644 index 0000000..c48e5ff --- /dev/null +++ b/examples/src/version_subscription/mod.rs @@ -0,0 +1,42 @@ +#[cfg(test)] +mod tests { + use crate::simple_test_net::*; + use frame_support::{assert_ok, pallet_prelude::Weight}; + use xcm::latest::prelude::*; + use xcm_simulator::TestExt; + + /// Scenario: + /// Parachain A wants to know which version of Xcm the relay chain uses. + /// It sends the `SubscribeVersion` instruction to get the Xcm version of the relay chain + /// and to receive updates if the version changes. + /// When the parachain receives the version it unsubscribes from version updates. + #[test] + fn subscribe_and_unsubscribe_version() { + MockNet::reset(); + + let query_id_set = 1234; + let message = Xcm(vec![SubscribeVersion { + query_id: query_id_set, + max_response_weight: Weight::from_all(0), + }]); + + ParaA::execute_with(|| { + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); + }); + + ParaA::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![QueryResponse { + query_id: query_id_set, + response: Response::Version(3), + max_weight: Weight::from_all(0), + querier: None, + }])], + ); + + let unsub_message = Xcm(vec![UnsubscribeVersion]); + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, unsub_message)); + }); + } +} diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 77e9dee..843e1b0 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -34,12 +34,12 @@ - [Channels and Bridges](journey/channels-and-bridges.md) - [Misc]() - [Config Deep Dive](executor_config/README.md) -- [Testing]() +- [Testing](testing/README.md) - [Separation of concerns]() - [Simulating message execution]() - [How to test my own configuration]() - [Testing the full XCM Journey]() -- [Transport Protocols]() +- [Transport Protocols](transport_protocols/README.md) - [VMP]() - [HRMP]() - [XCMP]() diff --git a/src/fundamentals/README.md b/src/fundamentals/README.md index 5d6b6bd..e0ff4e5 100644 --- a/src/fundamentals/README.md +++ b/src/fundamentals/README.md @@ -1,6 +1,2 @@ # Fundamentals -In this chapter we explore all the fundamentals that you should understand before diving deeper into XCM: -- [MultiLocation](./multilocation/index.md) -- [MultiAsset](./multiasset.md) -- [Responses](./responses.md) -- [Fees](./fees.md) \ No newline at end of file +In this chapter we explore all the fundamentals that you should understand before diving deeper into XCM. diff --git a/src/fundamentals/xcvm.md b/src/fundamentals/xcvm.md index bbdf083..83216ff 100644 --- a/src/fundamentals/xcvm.md +++ b/src/fundamentals/xcvm.md @@ -1 +1,8 @@ # XCVM + +We've already seen an overview of the XCVM. +In this section, we'll dive deeper into how it works. + +## Coming soon + +This chapter is still being worked on. diff --git a/src/journey/expects.md b/src/journey/expects.md index efa8929..3cf45f8 100644 --- a/src/journey/expects.md +++ b/src/journey/expects.md @@ -17,7 +17,9 @@ ExpectAsset(MultiAssets) ``` ### Example -For the full example, check [here](TODO). + +For the full example, check [here](https://github.com/paritytech/xcm-docs). + ```rust, noplayground WithdrawAsset((Here, AMOUNT).into()), BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, @@ -42,7 +44,8 @@ ExpectOrigin(Option) ``` ### Example -For the full example, check [here](TODO). + +For the full example, check [here](https://github.com/paritytech/xcm-docs). The `ExpectOrigin` instruction errors because the `ClearOrigin` clears the origin register and we expect it to be equal to `Parachain(1)`. ```rust,noplayground // Set the instructions that are executed when ExpectOrigin does not pass. @@ -78,7 +81,7 @@ ExpectPallet { ``` ### Example -For the full example, check [here](TODO). +For the full example, check [here](https://github.com/paritytech/xcm-docs). ```rust, noplayground // Set the instructions that are executed when ExpectPallet does not pass. // In this case, reporting back an error to the Parachain. @@ -108,7 +111,9 @@ The `ExpectError` instruction allows to only execute the instructions in the err ``` ### Example -For the full example, check [here](TODO). + +For the full example, check [here](https://github.com/paritytech/xcm-docs). + ```rust,noplayground SetErrorHandler(Xcm(vec![ ExpectError(Some((1, XcmError::VersionIncompatible))), @@ -143,8 +148,10 @@ pub enum MaybeErrorCode { ``` ### Example -For the full example, check [here](TODO). + +For the full example, check [here](https://github.com/paritytech/xcm-docs). The transact status is reported to `Parachain(1)` if the call in the `Transact` errors. + ```rust,noplayground SetErrorHandler(Xcm(vec![ReportTransactStatus(QueryResponseInfo { destination: Parachain(1).into(), diff --git a/src/journey/fees/README.md b/src/journey/fees/README.md index 73ae80f..12e2f4b 100644 --- a/src/journey/fees/README.md +++ b/src/journey/fees/README.md @@ -11,7 +11,7 @@ BuyExecution { fees: MultiAsset, weight_limit: WeightLimit } This instruction is used to buy weight using fees. While in some cases there's no need to pay for execution (if you control both systems for example), in most cases you'll need to add this instruction. -There's a predefined [barrier](../../config/barrier.md), `AllowTopLevelPaidExecutionFrom`, that explicitly drops messages that do not include this instruction. +There's a predefined [barrier](../../executor_config/index.md), `AllowTopLevelPaidExecutionFrom`, that explicitly drops messages that do not include this instruction. Let's grab the teleport message from the [transfers chapter](../transfers/teleports.md) and add fee payment. @@ -67,7 +67,7 @@ UnpaidExecution { weight_limit: WeightLimit, check_origin: Option This instruction is used for explicitly stating this message shouldn't be paid for. It can be used as a way of identifying certain priviledged messages that don't pay fees, coming from a particular system. -This instruction can be searched for in [barriers](TODO:add_link) to allow this. +This instruction can be searched for in [barriers](../../executor_config/index.md) to allow this. Make sure you trust the origin system because it won't be paying fees. There's already a predefined barrier in xcm-builder, `AllowExplicitUnpaidExecutionFrom`, that makes sure this is the first instruction in the message. As always, you can build your own for your own use-cases. @@ -85,7 +85,7 @@ Refunds any surplus weight previously bought with `BuyExecution`. This is useful in many cases: - When you pay for execution of your whole message, but there's an error and not all instructions get executed - When you set an error handler, buy weight for it, but in the end there's no error so it doesn't get called -- When you use the [`Transact` instruction]() and the call takes less weight than expected +- When you use the [`Transact` instruction](../transact.md) and the call takes less weight than expected ### Example @@ -109,4 +109,4 @@ let message = Xcm(vec![ In this example, we pay upfront for all the transactions we do later with the `DepositAsset` instructions. If any transaction throws an error (for example, due to lack of funds), the error handler will be called and the weight for all the instructions that weren't executed is refunded. -For the full example, check our [examples repo](TODO:add_link). +For the full example, check our [repo](https://github.com/paritytech/xcm-docs). diff --git a/src/journey/holding-modifiers.md b/src/journey/holding-modifiers.md index 78cef6f..940ba1f 100644 --- a/src/journey/holding-modifiers.md +++ b/src/journey/holding-modifiers.md @@ -11,7 +11,7 @@ BurnAsset(MultiAssets) The `BurnAsset` instruction allows for the reduction of assets in the Holding Register by up to the specified assets. The execution of the instruction does not throw an error if the Holding Register does not contain the assets (to make this an error, use `ExpectAsset` prior). ### Example -For the full example, check [here](TODO). +For the full example, check [the repo](https://github.com/paritytech/xcm-docs). The Scenario of the example is as follows: Parachain A withdraws 10 units from its sovereign account on the relay chain and burns 4 of them. The relay chain then reports back the status of the Holding Register to Parachain A. We expect the Holding Register to hold 6 units. @@ -53,7 +53,7 @@ and receive accordingly more assets then stated in `want`. If the `maximal` fiel order to receive as little as possible while receiving at least `want`. ### Example -The full example can be found [here](TODO). +The full example can be found in [the repo](https://github.com/paritytech/xcm-docs). The scenario for the example is this: Scenario: diff --git a/src/journey/locks/locks.md b/src/journey/locks/locks.md index 32a031f..9cdac59 100644 --- a/src/journey/locks/locks.md +++ b/src/journey/locks/locks.md @@ -63,7 +63,7 @@ This principle becomes more clear in the second example. ### Example 1 -Check out the full [example code](TODO). +Check out the full [example code](https://github.com/paritytech/xcm-docs). The scenario of this example is as follows: Parachain A locks 5 Cents of relay chain native assets of its Sovereign account on the relay chain and assigns Parachain B as unlocker. @@ -113,7 +113,7 @@ assert_eq!( ### Example 2 -Check out the full [example code](TODO). +Check out the full [example code](https://github.com/paritytech/xcm-docs). The scenario of this example is as follows: Parachain A sets two locks on the relay chain with as unlockers Parachain B and Parachain C. diff --git a/src/journey/origins.md b/src/journey/origins.md index 9723947..26ca91c 100644 --- a/src/journey/origins.md +++ b/src/journey/origins.md @@ -1,4 +1,5 @@ -# Origins +# Origin manipulation + An XCVM contains contextual information while executing XCM instructions. It uses the `XcmContext` struct to provide them. `XcmContext` contains information such as the origin of the corresponding XCM, the hash of the message, and the topic of the XCM. diff --git a/src/journey/queries.md b/src/journey/queries.md index 723b635..a32db8a 100644 --- a/src/journey/queries.md +++ b/src/journey/queries.md @@ -65,7 +65,9 @@ ReportHolding { response_info: QueryResponseInfo, assets: MultiAssetFilter } The `ReportHolding` instruction reports to the given destination the contents of the Holding Register. The `assets` field is 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*. For example, if the holding register contains 10 units of some fungible asset and the `assets` field specifies 15 units of the same asset, the result will return 10 units of that asset. Wild cards can be used to describe which assets in the holding register to report, but the response always contains assets and no wild cards. ### Example -For the full example, check [here](TODO). Assets are withdrawn from the account of parachain 1 on the relay chain and partly deposited in the account of parachain 2. The remaining assets are reported back to parachain 1. + +For the full example, check [here](https://github.com/paritytech/xcm-docs). Assets are withdrawn from the account of parachain 1 on the relay chain and partly deposited in the account of parachain 2. The remaining assets are reported back to parachain 1. + ```rust, noplayground Xcm(vec![ WithdrawAsset((Here, AMOUNT).into()), @@ -107,7 +109,7 @@ pub struct PalletInfo { ``` ### Example -For the full example, check [here](TODO). It queries for all instances of pallet_balances and sends the result back to parachain 1. +For the full example, check [here](https://github.com/paritytech/xcm-docs). It queries for all instances of pallet_balances and sends the result back to parachain 1. ```rust, noplayground Xcm(vec![ @@ -131,7 +133,7 @@ ReportError(QueryResponseInfo) ``` ### Example -For the full example, check [here](TODO). The message sets the error handler to report back any error that is thrown during execution of the instructions using the `ReportError` instruction. +For the full example, check [here](https://github.com/paritytech/xcm-docs). The message sets the error handler to report back any error that is thrown during execution of the instructions using the `ReportError` instruction. ```rust, noplayground Xcm(vec![ // Set the Error Handler to report back status of Error register. @@ -154,8 +156,10 @@ ReportTransactStatus(QueryResponseInfo) ``` ### Example -For the full example, check [here](TODO). + +For the full example, check [here](https://github.com/paritytech/xcm-docs). Dispatches a call on the consensus system receiving this Xcm and reports back the status of the Transact Status Register. + ```rust,noplayground Xcm(vec![ Transact { diff --git a/src/journey/register-modifiers.md b/src/journey/register-modifiers.md index 20b46b2..7622a5f 100644 --- a/src/journey/register-modifiers.md +++ b/src/journey/register-modifiers.md @@ -11,13 +11,13 @@ In the previous chapters we already saw instructions that modified the XCVM regi ```rust SetErrorHandler(Xcm) ``` -The `SetErrorHandler` instructions is used to set the Error Handler Register. As discussed in the [XCVM chapter](TODO), the Error Handler is executed when an error is thrown during the regular instruction execution. +The `SetErrorHandler` instructions is used to set the Error Handler Register. As discussed in the [XCVM chapter](../fundamentals/xcvm.md), the Error Handler is executed when an error is thrown during the regular instruction execution. ## SetAppendix ```rust SetAppendix(Xcm) ``` -The `SetAppendix` instruction is used to set the Appendix Register. As discussed in the [XCVM chapter](TODO), the Appendix instructions are executed after the regular and error handler instruction are executed. These instructions are executed regardless of whether an error occurred. +The `SetAppendix` instruction is used to set the Appendix Register. As discussed in the [XCVM chapter](../fundamentals/xcvm.md), the Appendix instructions are executed after the regular and error handler instruction are executed. These instructions are executed regardless of whether an error occurred. ## ClearError ```rust diff --git a/src/journey/transact.md b/src/journey/transact.md index 80bb265..366c8d4 100644 --- a/src/journey/transact.md +++ b/src/journey/transact.md @@ -1,4 +1,5 @@ # Transact + XCM contains an instruction that allows for the execution of calls (from a `RuntimeCall` in a FRAME-based system, to a smart contract function call in an EVM-based system) in a consensus system. It is the `Transact` instruction and it looks like this: @@ -15,7 +16,7 @@ The `origin_kind` is of type [OriginKind](https://paritytech.github.io/polkadot/ In the xcm-executor, the `origin_kind` is used to determine how to convert a `MultiLocation` origin into a `RuntimeOrigin`. For more information, check out the [xcm-executor config docs](../executor_config/index.html). -The `require_weight_at_most` field tells the XCVM executing the call how much [weight](../fundamentals/fees.md) it can use. +The `require_weight_at_most` field tells the XCVM executing the call how much [weight](../fundamentals/weight_and_fees.md) it can use. If the call uses more weight than the specified `require_weight_at_most`, the execution of the call fails. The `call` field is of type `DoubleEncoded`. @@ -52,7 +53,7 @@ It executes, among other things, the following steps: ## Example 1 -For the full example, check [here](TODO). +For the full example, check [the repo](https://github.com/paritytech/xcm-docs). In this example, the relay chain executes the `set_balance` function of `pallet_balances` on `Parachain(1)`. This function requires the origin to be root. We enable the root origin for the relay chain by setting `ParentAsSuperuser` for the `OriginConverter` config type. @@ -77,7 +78,7 @@ let message = Xcm(vec![ ``` ## Example 2 -For the full example, check [here](TODO). +For the full example, check [the repo](https://github.com/paritytech/xcm-docs). In this example, as Parachain(1), we create an NFT collection on the relay chain and we then mint an NFT with ID 1. The admin for the nft collection is parachain(1). The call looks as follows: diff --git a/src/journey/transfers/README.md b/src/journey/transfers/README.md index f7d2284..536f5db 100644 --- a/src/journey/transfers/README.md +++ b/src/journey/transfers/README.md @@ -1,8 +1,8 @@ # Transfers The first feature you'll be interested in when dealing with XCM is being able to transfer assets between consensus systems. -In the [Quickstart](../../overview/README.md) section, we saw a simple XCM that when executed, would send assets between two accounts on the same consensus system. -Now that we've learnt the [fundamentals](../../fundamentals/README.md), let's go over those same instructions. +In the [quickstart](../../quickstart/index.md) chapter, we saw a simple XCM that when executed, would send assets between two accounts on the same consensus system. +Now that we've learnt the [fundamentals](../../fundamentals/index.md), let's go over those same instructions once again. ## WithdrawAsset @@ -22,7 +22,7 @@ BuyExecution { fees: MultiAssets, weight_limit: WeightLimit }, Because XCM is designed to be agnostic to the underlying consensus system, it doesn't have fee payment baked in. This instruction lets you pay for the execution of the XCM using the assets in the holding register. -Most XCMs are not allowed to be executed (blocked by the [barrier](TODO:link)) if they don't contain this instruction as one of the first ones to pay for all future ones. +Most XCMs are not allowed to be executed (blocked by the [barrier](../../executor_config/index.md)) if they don't contain this instruction as one of the first ones to pay for all future ones. ## DepositAsset @@ -47,7 +47,7 @@ let message = Xcm(vec![ ``` As we've seen, the above message results in withdrawing assets from the origin of the message, paying for execution and depositing the rest to another account on the same system. -The full example can be seen in [the examples repo](TODO:add_link). +The full example can be seen in [the repo](https://github.com/paritytech/xcm-docs). ## Transferring between systems diff --git a/src/journey/transfers/reserve.md b/src/journey/transfers/reserve.md index c9a6817..d6b80c2 100644 --- a/src/journey/transfers/reserve.md +++ b/src/journey/transfers/reserve.md @@ -115,7 +115,7 @@ ReserveAssetDeposited(MultiAssets) Parachain 2 receives the XCM, mints new derivative tokens and deposit them locally to the beneficiary account. `ReserveAssetDeposited` is a *trusted indication*. As is the case with teleporting, you need to trust the reserve to have actually put the specified amount of assets in the sovereign account of this system. -You can specify which systems you trust as reserves for which assets by configuring the [IsReserve](TODO:add_link) type in the executor. +You can specify which systems you trust as reserves for which assets by configuring the [IsReserve](../../executor_config/index.md) type in the executor. In our example, both parachains trust the relay chain as a reserve for its own native token. ## Another example diff --git a/src/journey/transfers/teleports.md b/src/journey/transfers/teleports.md index efedf45..3722a1d 100644 --- a/src/journey/transfers/teleports.md +++ b/src/journey/transfers/teleports.md @@ -81,7 +81,7 @@ This instruction is a *trusted indication*. It should only be executed if the or This level of care must be taken because this instruction will *put assets into the circulating supply*, usually minting them. As specified earlier, this can result in an increase/decrease in circulating supply of an asset, or a duplication/loss of an NFT, if the source is not trusted for this purpose. -You can set which origins are allowed (act as teleporters) by configuring the [IsTeleporter](TODO:add_link) type in the XCM executor. +You can set which origins are allowed (act as teleporters) by configuring the [IsTeleporter](../../executor_config/index.md) type in the XCM executor. If the origin is not allowed to teleport assets to this system, an `UntrustedTeleportLocation` error is returned. This instruction will populate the holding register with the teleported assets, which can be used by further instructions. diff --git a/src/journey/version.md b/src/journey/version.md index ff8e5e3..de3c31c 100644 --- a/src/journey/version.md +++ b/src/journey/version.md @@ -4,7 +4,7 @@ XCM is a versioned messaging format. One version may contain more or different i - `UnsubscribeVersion` The version subscription model can differ per XCVM implementation. -The `xcm-executor` has a `SubscriptionService` [config item](../executor_config/README.md). +The `xcm-executor` has a `SubscriptionService` [config item](../executor_config/index.md). Any type specified as the `SubscriptionService` must implement the `VersionChangeNotifier` trait. The XCM pallet is one such implementor. When the `SubscribeVersion` instruction is sent to a consensus system that uses the XCM pallet as the `SubscriptionService` in the XCM executor, the system will send back its currently `AdvertisedVersion` and will keep the subscribed location up to date when the version changes. @@ -20,6 +20,4 @@ SubscribeVersion { UnsubscribeVersion ``` -Check out the [example](TODO). - - +Check out the [example](https://github.com/paritytech/xcm-docs). diff --git a/src/overview/README.md b/src/overview/README.md index 0223581..4bd40f6 100644 --- a/src/overview/README.md +++ b/src/overview/README.md @@ -4,13 +4,18 @@ XCM allows for different consensus systems to communicate with each other. This allows things like: - Sending tokens from one chain to another - Locking assets on one chain in order to gain some benefit on a smart contract on another chain -- Calling extrinsics on another chain +- Calling functions (extrinsics) on another chain But that's just the beginning. The true power of XCM comes from its composability. Once you can communicate with other consensus systems, you can get creative and implement whatever use case you need. This is especially true in the context of an ecosystem of highly specialized chains, like Polkadot. +Decentralized distributed systems are very complex, so when building interactions between them, it's easy to make mistakes. +Because of that, the end-user is not expected to write custom XCMs from scratch for all the interactions they want to achieve. +Instead, builders will use XCM to create enticing products that provide a good and safe user experience. +This is usually done by carefully thinking and testing the interaction, then packaging it into your system's runtime logic (via an extrinsic or smart contract for example), and exposing that functionality to users. + In this chapter, we will cover what XCM is, what it isn't, why it matters, and delve into the different components that make up the XCM ecosystem. Let's begin. diff --git a/src/overview/architecture.md b/src/overview/architecture.md index bf435ce..8f45b3b 100644 --- a/src/overview/architecture.md +++ b/src/overview/architecture.md @@ -23,14 +23,17 @@ The XCM executor follows the Cross-Consensus Virtual Machine (XCVM) specificatio The XCM executor is highly configurable. XCM builder provides building blocks people can use to configure their executor according to their needs. +Many of these building blocks will be explained in the [Config Deep Dive](../executor_config/index.md) chapter. +They cover common use-cases but are not meant to be exhaustive. +It's very easy to build your own building blocks for your specific configuration when needed, using these as examples. ## Pallet -The XCM pallet is a FRAME pallet that can be used to execute XCMs locally or send them to a different system. +The XCM pallet is a [FRAME](https://docs.substrate.io/quick-start/substrate-at-a-glance/) pallet that can be used to execute XCMs locally or send them to a different system. It also has extrinsics for specific use cases such as teleporting assets or doing reserve asset transfers, which we'll talk about later. It's the glue between XCM and FRAME, which is highly used in the Polkadot ecosystem. ## Simulator The simulator allows for testing XCMs fast, without needing to boot up several different nodes in a network, or test in production. -It's a very useful tool which we'll use later to build and test different XCMs. +It's a very useful tool which we'll use throughout this document to build and test different XCMs. diff --git a/src/overview/format.md b/src/overview/format.md index d980703..da3ce0e 100644 --- a/src/overview/format.md +++ b/src/overview/format.md @@ -3,7 +3,9 @@ It's essential to understand that XCM is a format, not a protocol. It describes how messages should be structured and contains instructions relevant to the on-chain actions the message intends to perform. However, XCM does not dictate how messages are delivered. -That responsibility falls on [transport layer protocols](TODO:link) such as XCMP (Cross Chain Message Passing) and VMP (Vertical Message Passing) in the Polkadot ecosystem, or any others to come. +That responsibility falls on [transport layer protocols](../transport_protocols/index.md) such as XCMP (Cross Chain Message Passing) and VMP (Vertical Message Passing) in the Polkadot ecosystem, or any others to come. + +This separation of concerns is useful, since it allows us to think of the interactions we want to build between systems without having to think about how the messages involved are actually routed. XCM is similar to how RESTful services use REST as an architectural style of development, where HTTP requests contain specific parameters to perform some action. Similar to UDP, out of the box XCM is a "fire and forget" model, unless there is a separate XCM message designed to be a response message which can be sent from the recipient to the sender. All error handling should also be done on the recipient side. diff --git a/src/overview/interoperability.md b/src/overview/interoperability.md index 7b8ed15..3760425 100644 --- a/src/overview/interoperability.md +++ b/src/overview/interoperability.md @@ -1,7 +1,7 @@ # Introduction XCM is a messaging format, a language, designed to enable seamless communication between different consensus systems, for example blockchains and smart contracts. -XCM was originally developed for the [Polkadot](https://polkadot.network/) ecosystem, but was designed to provide a common language for cross-consensus communication that can be used anywhere. +XCM was originally developed for the [Polkadot](https://polkadot.network/) ecosystem, but was designed to be general enough to provide a common language for cross-consensus communication that can be used anywhere. XCM is a language in which interactions (programs) can be written. It aims to provide better interoperability between consensus systems, both more features and a better user and developer experience. diff --git a/src/overview/xcvm.md b/src/overview/xcvm.md index ec89f96..230ffed 100644 --- a/src/overview/xcvm.md +++ b/src/overview/xcvm.md @@ -7,17 +7,20 @@ During execution, state is tracked in domain-specific registers, and is constant Most of the XCM format comprises these registers and the instructions used to compose XCVM programs. Like XCM, the XCVM is also a specification. -The first implementation is [xcm-executor](https://github.com/paritytech/polkadot/tree/master/xcm/xcm-executor), provided by Parity. +The implementation that will be used in this documentation is the [xcm-executor](https://github.com/paritytech/polkadot/tree/master/xcm/xcm-executor), built in Rust, provided by Parity. It's built to be highly configurable, with its building blocks available in [xcm-builder](https://github.com/paritytech/polkadot/tree/master/xcm/xcm-builder). -Configuring the executor is an important and extensive topic, one we will dive into further in [Config Deep Dive](TODO:link). -It's entirely possible to create another implementation of the XCVM if desired. +Configuring the executor is an important and extensive topic, one we will dive into further in the [Config Deep Dive](../executor_config/index.md) chapter. + +Anyone is free to make an implementation of the XCVM. +As long as they follow the standard, they'll be able to send XCMs to systems using other implementations. +Implementations in different programming languages will need to be used to bring XCM to other ecosystems. Typically, an XCM takes the following path through the XCVM: - Instructions within an XCM are read one-by-one by the XCVM. An XCM may contain one or more instructions. - The instruction is executed. This means that the current values of the XCVM registers, the instruction type, and the instruction operands are all used to execute some operation, which might result in some registers changing their value, or in an error being thrown, which would halt execution. - Each subsequent instruction within the XCM is read until the end of the message has been reached. -The XCVM register you will hear most about is the `Holding` register. +The XCVM register you will hear most about is the `holding` register. An XCVM program that handles assets (which means most of them) will be putting them in and taking them out of this register. Instructions we'll see later like `DepositAsset`, `WithdrawAsset` and many more, make use of this register. You can see all registers in the [All XCVM Registers](TODO:link) section. diff --git a/src/quickstart/README.md b/src/quickstart/README.md index 259fe46..67839c3 100644 --- a/src/quickstart/README.md +++ b/src/quickstart/README.md @@ -3,15 +3,16 @@ The XCM code can be found in [polkadot repository](https://github.com/paritytech/polkadot/tree/master/xcm). ## Rust & Cargo -A pre-requisite for using XCM is to have a stable Rust version and Cargo installed. Here's an [installation guide](https://docs.substrate.io/install/) on how to install rust and cargo. +A pre-requisite for using XCM is to have a stable Rust version and Cargo installed. Here's an [installation guide](https://docs.substrate.io/install/). ## Running the Examples -All examples in the documentation are located in the [examples repository](). Follow these steps to run the `first-look` example. First clone the repository: +All examples in the documentation are located in the [repository](https://github.com/paritytech/xcm-docs). Follow these steps to run the `first-look` example. +First clone the repository: ```shell -git clone git@github.com:vstam1/xcm-examples.git -cd xcm-examples +git clone git@github.com:paritytech/xcm-docs.git +cd xcm-docs/examples ``` To run the first-look example, run the following line: diff --git a/src/quickstart/first-look.md b/src/quickstart/first-look.md index 98d4471..cecae90 100644 --- a/src/quickstart/first-look.md +++ b/src/quickstart/first-look.md @@ -1,5 +1,6 @@ # First Look -In this section, we take you through a simple example of an XCM. In this example, we withdraw the native token from the account of Alice and deposit this token in the account of Bob. This message simulates a transfer between two accounts in the same consensus system (`ParaA`). Find here the [code example](). +In this section, we take you through a simple example of an XCM. In this example, we withdraw the native token from the account of Alice and deposit this token in the account of Bob. This message simulates a transfer between two accounts in the same consensus system (`ParaA`). You can find the complete code example [in the repo](https://github.com/paritytech/xcm-docs). + ## Message ```rust,noplayground let message = Xcm(vec![ @@ -17,21 +18,29 @@ In this section, we take you through a simple example of an XCM. In this example } ]); ``` -The message consists of three instructions: WithdrawAsset, BuyExecution, and DepositAsset. In the following sections we will go over each of these instructions. + +The message consists of three instructions: `WithdrawAsset`, `BuyExecution`, and `DepositAsset`. +In the following sections we will go over each instruction. ### WithdrawAsset ```rust WithdrawAsset((Here, amount).into()) ``` -The first instruction takes as an input the [MultiAsset]() that should be withdrawn. The MultiAsset describes the native parachain token with the `Here` keyword. The `amount` parameter is the number of tokens that are transferred. The withdrawal account depends on the Origin of the message. In this example the Origin of the message is Alice. -The WithdrawAsset instruction moves `amount` number of native tokens from Alice's account into the `Holding register`. +The first instruction takes as an input the [MultiAsset]() that should be withdrawn. The MultiAsset describes the native parachain token with the `Here` keyword. The `amount` parameter is the number of tokens that are transferred. The withdrawal account depends on the origin of the message. In this example the origin of the message is Alice. +The WithdrawAsset instruction moves `amount` number of native tokens from Alice's account into the _holding register_. ### BuyExecution ```rust BuyExecution{fees: (Here, amount).into(), weight_limit: WeightLimit::Unlimited} ``` -To execute XCM instructions, weight (some kind of resources) has to be bought. The amount of weight depends on the number and type of instructions in the XCM. The `BuyExecution` instruction pays for the weight using the `fees`. The `fees` parameter describes the asset in the `Holding register` that should be used for paying for the weight. The `weight_limit` defines the maximum amount of fees that can be used for buying weight. There are special occasions where it is not necessary to buy weight. See [fees]() for more information about the fees in XCM. +To execute XCM instructions, weight (some amount of resources) has to be bought. +The amount of weight needed to execute an XCM depends on the number and type of instructions in the XCM. +The `BuyExecution` instruction pays for the weight using the `fees`. +The `fees` parameter describes the asset in the _holding register_ that should be used for paying for the weight. +The `weight_limit` parameter defines the maximum amount of fees that can be used for buying weight. +There are special occasions where it is not necessary to buy weight. +See the chapter on [weight and fees](../fundamentals/weight_and_fees.md) for more information about the fees in XCM. ### DepositAsset ```rust @@ -46,13 +55,17 @@ DepositAsset { }.into() } ``` -The DepositAsset instruction is used to deposit funds from the holding register into the account of the `beneficiary`. We don’t actually know how much is remaining in the Holding Register after the BuyExecution instruction, but that doesn’t matter since we specify a wildcard for the asset(s) which should be deposited. In this case, the wildcard is `All`, meaning that all assets in the Holding register should be deposited. The `beneficiary` in this case is the account of Bob in the current consensus system. - -When the three instructions are combined, we withdraw `amount` native tokens from the account of Alice, pay for the execution of the instructions, and deposit the remaining tokens in the account of Bob. +The DepositAsset instruction is used to deposit funds from the holding register into the account of the _beneficiary_. +We don’t actually know how much is remaining in the holding register after the `BuyExecution` instruction, but that doesn’t matter since we specify a wildcard for the asset(s) which should be deposited. +In this case, the wildcard is `All`, meaning that all assets in the holding register at that point in the execution should be deposited. +The _beneficiary_ in this case is the account of Bob in the current consensus system. +When the three instructions are combined, we withdraw `amount` native tokens from the account of Alice, pay for the execution of these instructions, and deposit the remaining tokens in the account of Bob. ## What next? -Now that we have taken a first look at an XCM, we can dive deeper into all the XCM instructions. -For an overview of the instructions check out the [xcm-format](https://github.com/paritytech/xcm-format#5-the-xcvm-instruction-set). -Or check out examples for each of the instruction in [A Journey through XCM](). -To get a better understanding about MultiLocations, MultiAssets, and other fundamental concepts in XCM, check out the [fundamentals chapter](fundamentals/README.md). + +Now that we have taken a first look at an XCM, we can dive deeper into all the XCM instructions, to be able to build more complex XCVM programs. +For an overview of the instructions check out the [xcm-format repo](https://github.com/paritytech/xcm-format#5-the-xcvm-instruction-set). +We'll show examples for every instruction in the [journey through XCM](../journey/index.md) chapter. +First, it's important to learn the fundamentals, `MultiLocation`, `MultiAsset`, and other concepts in XCM. +We'll talk about those next. diff --git a/src/quickstart/xcm-simulator.md b/src/quickstart/xcm-simulator.md index 178bb77..fbb6b1e 100644 --- a/src/quickstart/xcm-simulator.md +++ b/src/quickstart/xcm-simulator.md @@ -1,13 +1,11 @@ # XCM Simulator -Setting up a live network with multiple connected parachains for testing XCM is not straight forward. The `XCM-simulator` was created as a solution to this problem. The XCM-simulator is a network simulator specifically designed for testing and playing around with XCM. It uses mock relay chain and parachain runtime. - -For testing xcm configurations for live runtime environments we use the `XCM-emulator`. The XCM-emulator can use production relay chain and parachain runtimes. Users can plug in Kusama, Statemine, or their custom runtime etc. With up-to-date chain specs, it's able to verify if specific XCM messages work in live networks. The specific use cases will be further explained in the chapter on [testing](testing/README.md). - -In the next section we will take a first look at an XCM. The XCM-simulator is used for the example code. - -[Next: First Look at an XCM](first-look.md) - - - +Setting up a live network with multiple connected parachains for testing XCM is not straight forward. +The `xcm-simulator` was created as a solution to this problem. +It's a network simulator specifically designed for testing and tinkering with XCM. +It uses mock runtimes for a relay chain and parachains. +Although it's a great tool to learn and test XCMs, it shouldn't be the only thing you use to actually test your XCM-powered solution. +We'll get into tools and best practices for testing in the [testing](../testing/index.md) chapter. +We'll use the simulator throughout the documentation to show different XCMs in action. +In the next section we will take a first look at an XCM. diff --git a/src/testing/README.md b/src/testing/README.md new file mode 100644 index 0000000..1c05856 --- /dev/null +++ b/src/testing/README.md @@ -0,0 +1,13 @@ +# Testing + +Before deploying your XCM-powered solution to production, it's paramount to test it thoroughly. +There are different levels for testing, which should be tackled sequentially: +- Message: Making sure your message works properly, according to the XCVM spec. +- Configuration: Making sure your executor's configuration is as expected. +- End-to-end: Making sure the whole flow works, in an environment as similar to production as possible. + +We'll discuss tools and best practices for each of these levels. + +## Coming soon + +This chapter is still being worked on. diff --git a/src/transport_protocols/README.md b/src/transport_protocols/README.md new file mode 100644 index 0000000..97a5a22 --- /dev/null +++ b/src/transport_protocols/README.md @@ -0,0 +1,8 @@ +# Transport Protocols + +When building a tool or product using XCM, you usually don't need to concern yourself with the transport protocols used to actually route the messages between systems. +However, if you need to learn about them or are just curious about how they work, we'll detail the existing ones in the Dotsama ecosystem. + +## Coming soon + +This chapter is still being worked on. diff --git a/src/xcm.md b/src/xcm.md index fd5f0e9..c32eee3 100644 --- a/src/xcm.md +++ b/src/xcm.md @@ -1,9 +1,10 @@ # XCM: Cross-Consensus Messaging -This is the place where we will brainstorm XCM documentation and tutorial ideas. -It will eventually be in a public centralized place for everyone to find and use. +Welcome to the XCM documentation! +Whether you're a developer, a blockchain enthusiast, or just interested in Polkadot, this guide aims to provide you with an easy-to-understand and comprehensive introduction to XCM. -## Draft intro +## Still under development -Welcome to the Cross-Consensus Message Format (XCM) documentation! -Whether you're a developer, a blockchain enthusiast, or just interested in Polkadot, this guide aims to provide you with an easy-to-understand and comprehensive introduction to XCM. +Keep in mind this documentation is still a work in progress. +We'll be polishing it and adding more content. +If there's anything in particular you'd like to see, or any pressing concern, please [open an issue](https://github.com/paritytech/xcm-docs/issues). From f8a3786a209f1d59ef5bef530af3ec397b131d43 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 25 May 2023 17:49:57 -0300 Subject: [PATCH 49/73] update to 0.9.42 and remove emulator setup for now --- examples/Cargo.toml | 90 ++++--- examples/src/expects/mod.rs | 9 +- .../src/kusama_test_net/kusama_test_net.rs | 176 ------------- examples/src/kusama_test_net/mod.rs | 2 - examples/src/kusama_test_net/yayoi.rs | 237 ------------------ examples/src/lib.rs | 1 - examples/src/simple_test_net/parachain.rs | 5 + examples/src/simple_test_net/relay_chain.rs | 5 + examples/src/transact/mod.rs | 5 +- 9 files changed, 60 insertions(+), 470 deletions(-) delete mode 100644 examples/src/kusama_test_net/kusama_test_net.rs delete mode 100644 examples/src/kusama_test_net/mod.rs delete mode 100644 examples/src/kusama_test_net/yayoi.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 63137d0..81a7e40 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -7,62 +7,60 @@ authors = ["Xcm Team"] [dependencies] bounded-collections = { version = "0.1.5", default-features = false } -smallvec = "1.4.0" -codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive", "max-encoded-len"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde_json = { version = "1.0.85" } +smallvec = "1.10.0" +codec = { package = "parity-scale-codec", version = "3.5.0", features = ["derive", "max-encoded-len"] } +scale-info = { version = "2.7.0", default-features = false, features = ["derive"] } +serde_json = { version = "1.0.96" } hex = { version = "0.4" } hex-literal = { version = "0.3.1" } libsecp256k1 = { version = "0.7" } #Polkadot -polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } -xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } -xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } -xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } -pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } -pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39"} -pallet-assets = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } -pallet-uniques = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } -pallet-nfts = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } -polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } -polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } -kusama-runtime = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } -kusama-runtime-constants = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } -xcm-simulator = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39" } +polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } +xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } +xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } +xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } +pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42"} +pallet-assets = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +pallet-uniques = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +pallet-nfts = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } +polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } +kusama-runtime = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } +kusama-runtime-constants = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } +xcm-simulator = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } # substrate -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } -sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } -sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } -sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } -sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } -sp-session = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } -sp-version = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } -sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +sp-session = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +sp-version = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } -cumulus-primitives-utility = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } -cumulus-primitives-timestamp = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } -cumulus-pallet-parachain-system = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } -cumulus-pallet-dmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } -cumulus-pallet-xcmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } -cumulus-pallet-xcm = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } -parachain-info = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } -cumulus-primitives-parachain-inherent = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } -cumulus-test-relay-sproof-builder = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } -statemine-runtime = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39" } - -xcm-emulator = { git = "https://github.com/shaunxw/xcm-simulator", rev = "aa13dce47596e150806dfc3af99096dae6ffc65e" } +cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } +cumulus-primitives-utility = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } +cumulus-primitives-timestamp = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } +cumulus-pallet-parachain-system = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } +cumulus-pallet-dmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } +cumulus-pallet-xcmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } +cumulus-pallet-xcm = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } +parachain-info = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } +cumulus-primitives-parachain-inherent = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } +cumulus-test-relay-sproof-builder = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } +statemine-runtime = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } [dev-dependencies] env_logger = "0.9.0" log = "0.4.17" -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } -sp-trie = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +sp-trie = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } diff --git a/examples/src/expects/mod.rs b/examples/src/expects/mod.rs index 976d9a5..6714d30 100644 --- a/examples/src/expects/mod.rs +++ b/examples/src/expects/mod.rs @@ -184,19 +184,18 @@ mod tests { /// If the status was not succesful, the `ExpectTransactStatus` errors, /// and the ErrorHandler will report the error back to the Parachain. /// - /// Assert that `set_balance` execution fails as it requires the origin to be root, + /// Assert that `force_set_balance` execution fails as it requires the origin to be root, /// and the origin_kind is `SovereignAccount`. #[test] fn expect_transact_status() { MockNet::reset(); // Runtime call dispatched by the Transact instruction. - // set_balance requires root origin. + // force_set_balance requires root origin. let call = relay_chain::RuntimeCall::Balances(pallet_balances::Call::< relay_chain::Runtime, - >::set_balance { + >::force_set_balance { who: ALICE, new_free: 100, - new_reserved: 0, }); let message = Xcm(vec![ @@ -219,7 +218,7 @@ mod tests { assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); }); - // The execution of set_balance does not succeed, and error is reported back to the parachain. + // The execution of force_set_balance does not succeed, and error is reported back to the parachain. ParaA::execute_with(|| { assert_eq!( parachain::MsgQueue::received_dmp(), diff --git a/examples/src/kusama_test_net/kusama_test_net.rs b/examples/src/kusama_test_net/kusama_test_net.rs deleted file mode 100644 index 5c3153c..0000000 --- a/examples/src/kusama_test_net/kusama_test_net.rs +++ /dev/null @@ -1,176 +0,0 @@ -use crate::kusama_test_net::yayoi; -pub use codec::{Decode, Encode}; -use frame_support::{pallet_prelude::Weight, traits::GenesisBuild}; -use polkadot_primitives::v2::{BlockNumber, MAX_CODE_SIZE, MAX_POV_SIZE}; -use polkadot_runtime_parachains::configuration::HostConfiguration; -use sp_runtime::AccountId32; -pub use xcm::v3::prelude::*; -use xcm_emulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; -use xcm_executor::traits::Convert; - -pub const ALICE: AccountId32 = AccountId32::new([0u8; 32]); -#[allow(dead_code)] -pub const BOB: AccountId32 = AccountId32::new([1u8; 32]); -pub const INITIAL_BALANCE: u128 = 1_000_000_000_000; - -decl_test_relay_chain! { - pub struct KusamaNet { - Runtime = kusama_runtime::Runtime, - XcmConfig = kusama_runtime::xcm_config::XcmConfig, - new_ext = kusama_ext(), - } -} - -decl_test_parachain! { - pub struct Statemine { - Runtime = statemine_runtime::Runtime, - RuntimeOrigin = statemine_runtime::RuntimeOrigin, - XcmpMessageHandler = statemine_runtime::XcmpQueue, - DmpMessageHandler = statemine_runtime::DmpQueue, - new_ext = statemine_ext(1000), - } -} - -decl_test_parachain! { - pub struct SimpleParachain { - Runtime = yayoi::Runtime, - RuntimeOrigin = yayoi::RuntimeOrigin, - XcmpMessageHandler = yayoi::XcmpQueue, - DmpMessageHandler = yayoi::DmpQueue, - new_ext = yayoi_ext(1001), - } -} - -decl_test_parachain! { - pub struct SimpleParachain2 { - Runtime = yayoi::Runtime, - RuntimeOrigin = yayoi::RuntimeOrigin, - XcmpMessageHandler = yayoi::XcmpQueue, - DmpMessageHandler = yayoi::DmpQueue, - new_ext = yayoi_ext(1002), - } -} - -decl_test_network! { - pub struct TestNet { - relay_chain = KusamaNet, - parachains = vec![ - (1000, Statemine), - (1001, SimpleParachain), - (1002, SimpleParachain2), - ], - } -} - -fn default_parachains_host_configuration() -> HostConfiguration { - HostConfiguration { - minimum_validation_upgrade_delay: 5, - validation_upgrade_cooldown: 5u32, - validation_upgrade_delay: 5, - code_retention_period: 1200, - max_code_size: MAX_CODE_SIZE, - max_pov_size: MAX_POV_SIZE, - max_head_data_size: 32 * 1024, - group_rotation_frequency: 20, - chain_availability_period: 4, - thread_availability_period: 4, - max_upward_queue_count: 8, - max_upward_queue_size: 1024 * 1024, - max_downward_message_size: 1024, - ump_service_total_weight: Weight::from_ref_time(4 * 1_000_000_000), - max_upward_message_size: 50 * 1024, - max_upward_message_num_per_candidate: 5, - hrmp_sender_deposit: 0, - hrmp_recipient_deposit: 0, - hrmp_channel_max_capacity: 8, - hrmp_channel_max_total_size: 8 * 1024, - hrmp_max_parachain_inbound_channels: 4, - hrmp_max_parathread_inbound_channels: 4, - hrmp_channel_max_message_size: 1024 * 1024, - hrmp_max_parachain_outbound_channels: 4, - hrmp_max_parathread_outbound_channels: 4, - hrmp_max_message_num_per_candidate: 5, - dispute_period: 6, - no_show_slots: 2, - n_delay_tranches: 25, - needed_approvals: 2, - relay_vrf_modulo_samples: 2, - zeroth_delay_tranche_width: 0, - ..Default::default() - } -} - -pub fn kusama_ext() -> sp_io::TestExternalities { - use kusama_runtime::{Runtime, System}; - - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - - pallet_balances::GenesisConfig:: { balances: vec![(ALICE, INITIAL_BALANCE)] } - .assimilate_storage(&mut t) - .unwrap(); - - polkadot_runtime_parachains::configuration::GenesisConfig:: { - config: default_parachains_host_configuration(), - } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext -} - -pub fn statemine_ext(para_id: u32) -> sp_io::TestExternalities { - use statemine_runtime::{Runtime, System}; - - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - - let parachain_info_config = parachain_info::GenesisConfig { parachain_id: para_id.into() }; - - >::assimilate_storage( - ¶chain_info_config, - &mut t, - ) - .unwrap(); - - pallet_balances::GenesisConfig:: { - balances: vec![ - (ALICE, INITIAL_BALANCE), - (statemine_sibling_account_id(1001), INITIAL_BALANCE), - (statemine_sibling_account_id(1002), INITIAL_BALANCE), - ], - } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext -} - -pub fn yayoi_ext(para_id: u32) -> sp_io::TestExternalities { - use crate::kusama_test_net::yayoi::{Runtime, System}; - - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - - let parachain_info_config = parachain_info::GenesisConfig { parachain_id: para_id.into() }; - - >::assimilate_storage( - ¶chain_info_config, - &mut t, - ) - .unwrap(); - - pallet_balances::GenesisConfig:: { balances: vec![(ALICE, INITIAL_BALANCE)] } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext -} - -pub fn statemine_sibling_account_id(para: u32) -> sp_runtime::AccountId32 { - statemine_runtime::xcm_config::LocationToAccountId::convert((Parent, Parachain(para)).into()) - .unwrap() -} diff --git a/examples/src/kusama_test_net/mod.rs b/examples/src/kusama_test_net/mod.rs deleted file mode 100644 index 0c45b13..0000000 --- a/examples/src/kusama_test_net/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod kusama_test_net; -pub mod yayoi; diff --git a/examples/src/kusama_test_net/yayoi.rs b/examples/src/kusama_test_net/yayoi.rs deleted file mode 100644 index 5271816..0000000 --- a/examples/src/kusama_test_net/yayoi.rs +++ /dev/null @@ -1,237 +0,0 @@ -use frame_support::{ - construct_runtime, parameter_types, - traits::{ConstU32, Everything, Nothing}, - weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, -}; -use frame_system::EnsureRoot; -use pallet_xcm::XcmPassthrough; -use polkadot_parachain::primitives::Sibling; -use sp_core::H256; -use sp_runtime::{ - testing::Header, - traits::{Convert, IdentityLookup}, - AccountId32, -}; -use xcm::v3::prelude::*; -use xcm_builder::{ - AccountId32Aliases, AllowUnpaidExecutionFrom, CurrencyAdapter, EnsureXcmOrigin, - FixedWeightBounds, IsConcrete, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, -}; -use xcm_executor::{Config, XcmExecutor}; - -pub type AccountId = AccountId32; -pub type Balance = u128; - -parameter_types! { - pub const BlockHashCount: u64 = 250; -} - -impl frame_system::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Header = Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = Everything; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; - type MaxConsumers = ConstU32<16>; -} - -parameter_types! { - pub ExistentialDeposit: Balance = 1; - pub const MaxLocks: u32 = 50; - pub const MaxReserves: u32 = 50; -} - -impl pallet_balances::Config for Runtime { - type MaxLocks = MaxLocks; - type Balance = Balance; - type RuntimeEvent = RuntimeEvent; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type WeightInfo = (); - type MaxReserves = MaxReserves; - type ReserveIdentifier = [u8; 8]; -} - -impl parachain_info::Config for Runtime {} - -parameter_types! { - pub const RelayLocation: MultiLocation = MultiLocation::parent(); - pub const TokenLocation: MultiLocation = Here.into_location(); - pub const RelayNetwork: NetworkId = NetworkId::Kusama; - pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); - pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(RelayNetwork::get()), Parachain(ParachainInfo::parachain_id().into())); -} - -pub type LocationToAccountId = ( - ParentIsPreset, - SiblingParachainConvertsVia, - AccountId32Aliases, -); - -pub type XcmOriginToCallOrigin = ( - SovereignSignedViaLocation, - RelayChainAsNative, - SiblingParachainAsNative, - SignedAccountId32AsNative, - XcmPassthrough, -); - -parameter_types! { - pub const UnitWeightCost: u64 = 10; - pub const MaxInstructions: u32 = 100; - pub const MaxAssetsIntoHolding: u32 = 64; -} - -pub type LocalAssetTransactor = - CurrencyAdapter, LocationToAccountId, AccountId, ()>; - -/// The means for routing XCM messages which are not for local execution into -/// the right message queues. -pub type XcmRouter = ( - // Two routers - use UMP to communicate with the relay chain: - cumulus_primitives_utility::ParentAsUmp, - // ..and XCMP to communicate with the sibling chains. - XcmpQueue, -); - -pub type Barrier = AllowUnpaidExecutionFrom; - -pub struct XcmConfig; -impl Config for XcmConfig { - type RuntimeCall = RuntimeCall; - type XcmSender = XcmRouter; - type AssetTransactor = LocalAssetTransactor; - type OriginConverter = XcmOriginToCallOrigin; - type IsReserve = (); - type IsTeleporter = (); - type UniversalLocation = UniversalLocation; - type Barrier = Barrier; - type Weigher = FixedWeightBounds; - type Trader = (); - type ResponseHandler = (); - type AssetTrap = (); - type AssetClaims = (); - type SubscriptionService = (); - type AssetLocker = PolkadotXcm; - type AssetExchanger = (); - type PalletInstancesInfo = (); - type MaxAssetsIntoHolding = MaxAssetsIntoHolding; - type FeeManager = (); - type MessageExporter = (); - type UniversalAliases = Nothing; - type CallDispatcher = RuntimeCall; - type SafeCallFilter = Everything; -} - -parameter_types! { - pub const ReservedXcmpWeight: Weight = Weight::from_ref_time(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4)); - pub const ReservedDmpWeight: Weight = Weight::from_ref_time(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4)); -} - -impl cumulus_pallet_parachain_system::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type OnSystemEvent = (); - type SelfParaId = ParachainInfo; - type DmpMessageHandler = DmpQueue; - type ReservedDmpWeight = ReservedDmpWeight; - type OutboundXcmpMessageSource = XcmpQueue; - type XcmpMessageHandler = XcmpQueue; - type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; -} - -impl cumulus_pallet_xcmp_queue::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; - type ChannelInfo = ParachainSystem; - type VersionWrapper = (); - type ExecuteOverweightOrigin = EnsureRoot; - type ControllerOrigin = EnsureRoot; - type ControllerOriginConverter = XcmOriginToCallOrigin; - type WeightInfo = (); - type PriceForSiblingDelivery = (); -} - -impl cumulus_pallet_dmp_queue::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; - type ExecuteOverweightOrigin = EnsureRoot; -} - -impl cumulus_pallet_xcm::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; -} - -pub type LocalOriginToLocation = SignedToAccountId32; - -impl pallet_xcm::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type SendXcmOrigin = EnsureXcmOrigin; - type XcmRouter = XcmRouter; - type ExecuteXcmOrigin = EnsureXcmOrigin; - type XcmExecuteFilter = Everything; - type XcmExecutor = XcmExecutor; - type XcmTeleportFilter = Nothing; - type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; - type UniversalLocation = UniversalLocation; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; - type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; - type Currency = Balances; - type CurrencyMatcher = (); - type TrustedLockers = (); - type SovereignAccountOf = (); - type MaxLockers = ConstU32<8>; - type WeightInfo = pallet_xcm::TestWeightInfo; -} - -pub struct AccountIdToMultiLocation; -impl Convert for AccountIdToMultiLocation { - fn convert(account: AccountId) -> MultiLocation { - X1(Junction::AccountId32 { network: None, id: account.into() }).into() - } -} - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - ParachainSystem: cumulus_pallet_parachain_system::{Pallet, Call, Storage, Inherent, Config, Event}, - ParachainInfo: parachain_info::{Pallet, Storage, Config}, - XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event}, - DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event}, - CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin}, - PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin}, - } -); diff --git a/examples/src/lib.rs b/examples/src/lib.rs index 072e1ae..5a5a681 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -1,7 +1,6 @@ mod expects; mod first_look; mod holding_modifiers; -mod kusama_test_net; mod locks; mod origins; mod queries; diff --git a/examples/src/simple_test_net/parachain.rs b/examples/src/simple_test_net/parachain.rs index 8ec22c2..7891307 100644 --- a/examples/src/simple_test_net/parachain.rs +++ b/examples/src/simple_test_net/parachain.rs @@ -110,6 +110,10 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; + type HoldIdentifier = (); + type FreezeIdentifier = (); + type MaxHolds = ConstU32<0>; + type MaxFreezes = ConstU32<0>; } parameter_types! { @@ -522,6 +526,7 @@ impl pallet_xcm::Config for Runtime { type WeightInfo = pallet_xcm::TestWeightInfo; #[cfg(feature = "runtime-benchmarks")] type ReachableDest = ReachableDest; + type AdminOrigin = EnsureRoot; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; diff --git a/examples/src/simple_test_net/relay_chain.rs b/examples/src/simple_test_net/relay_chain.rs index 84552c6..8b8129a 100644 --- a/examples/src/simple_test_net/relay_chain.rs +++ b/examples/src/simple_test_net/relay_chain.rs @@ -89,6 +89,10 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; + type HoldIdentifier = (); + type FreezeIdentifier = (); + type MaxHolds = ConstU32<0>; + type MaxFreezes = ConstU32<0>; } impl pallet_uniques::Config for Runtime { @@ -221,6 +225,7 @@ impl pallet_xcm::Config for Runtime { type WeightInfo = pallet_xcm::TestWeightInfo; #[cfg(feature = "runtime-benchmarks")] type ReachableDest = ReachableDest; + type AdminOrigin = EnsureRoot; } parameter_types! { diff --git a/examples/src/transact/mod.rs b/examples/src/transact/mod.rs index b1b9841..d767570 100644 --- a/examples/src/transact/mod.rs +++ b/examples/src/transact/mod.rs @@ -15,12 +15,11 @@ mod tests { fn transact_set_balance() { MockNet::reset(); // Runtime call dispatched by the Transact instruction. - // set_balance requires root origin. + // force_set_balance requires root origin. let call = parachain::RuntimeCall::Balances( - pallet_balances::Call::::set_balance { + pallet_balances::Call::::force_set_balance { who: ALICE, new_free: 5 * AMOUNT, - new_reserved: 0, }, ); From 02a4778987d1697cf8303617b1b707856eb4eec8 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Thu, 25 May 2023 22:59:47 +0200 Subject: [PATCH 50/73] reference XCVM registers and remove responses from fundamentals (#21) --- src/SUMMARY.md | 2 +- src/fundamentals/responses.md | 1 - src/overview/xcvm.md | 2 +- src/reference/xcvm-registers.md | 15 +++++++++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) delete mode 100644 src/fundamentals/responses.md create mode 100644 src/reference/xcvm-registers.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 843e1b0..c3ac26f 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -48,6 +48,6 @@ - [Cookbook]() - [All Instructions]() -- [All XCVM Registers]() +- [All XCVM Registers](reference/xcvm-registers.md) [Next Steps]() diff --git a/src/fundamentals/responses.md b/src/fundamentals/responses.md deleted file mode 100644 index d39c07d..0000000 --- a/src/fundamentals/responses.md +++ /dev/null @@ -1 +0,0 @@ -Todo \ No newline at end of file diff --git a/src/overview/xcvm.md b/src/overview/xcvm.md index 230ffed..520a165 100644 --- a/src/overview/xcvm.md +++ b/src/overview/xcvm.md @@ -23,4 +23,4 @@ Typically, an XCM takes the following path through the XCVM: The XCVM register you will hear most about is the `holding` register. An XCVM program that handles assets (which means most of them) will be putting them in and taking them out of this register. Instructions we'll see later like `DepositAsset`, `WithdrawAsset` and many more, make use of this register. -You can see all registers in the [All XCVM Registers](TODO:link) section. +You can see all registers in the [All XCVM Registers](../reference/xcvm-registers.md) section. diff --git a/src/reference/xcvm-registers.md b/src/reference/xcvm-registers.md new file mode 100644 index 0000000..327b92c --- /dev/null +++ b/src/reference/xcvm-registers.md @@ -0,0 +1,15 @@ +# XCVM Registers + +Each implementation of an XCVM contains several registers which cannot generally be set at will, but rather begin with specific values and may only be mutated under certain circumstances and/or obeying certain rules. An XCVM has the following registers: + +- [Programme](https://github.com/paritytech/xcm-format#31-programme) +- [Programme Counter](https://github.com/paritytech/xcm-format#32-programme-counter) +- [Error](https://github.com/paritytech/xcm-format#33-error) +- [Error Handler](https://github.com/paritytech/xcm-format#34-error-handler) +- [Appendix](https://github.com/paritytech/xcm-format#35-appendix) +- [Origin](https://github.com/paritytech/xcm-format#36-origin) +- [Holding](https://github.com/paritytech/xcm-format#37-holding-register) +- [Surplus Weight](https://github.com/paritytech/xcm-format#38-surplus-weight) +- [Refunded Weight](https://github.com/paritytech/xcm-format#39-refunded-weight) +- [Transact Status](https://github.com/paritytech/xcm-format#310-transact-status) +- [Topic](https://github.com/paritytech/xcm-format#311-topic) \ No newline at end of file From 8f3c861ebb4d8f2e4085a1245b0b96461b5216b9 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 31 May 2023 12:42:25 -0300 Subject: [PATCH 51/73] Add more transfer examples (#24) * Add an example of para to relay reserve asset transfer * Enforce fees in barriers (#25) * Fix barriers --- examples/src/expects/mod.rs | 72 ++++++-- examples/src/first_look.rs | 11 +- examples/src/holding_modifiers/mod.rs | 10 +- examples/src/locks/mod.rs | 79 ++++++--- examples/src/queries/mod.rs | 66 ++++++-- examples/src/simple_test_net/mod.rs | 178 +++++++++++++++++++- examples/src/simple_test_net/parachain.rs | 81 ++++++--- examples/src/simple_test_net/relay_chain.rs | 68 ++++++-- examples/src/transact/mod.rs | 36 ++-- examples/src/transfers/reserve.rs | 151 ++++++++++++++--- examples/src/transfers/teleport.rs | 116 ++++--------- examples/src/trap_and_claim/mod.rs | 5 +- examples/src/version_subscription/mod.rs | 11 +- 13 files changed, 646 insertions(+), 238 deletions(-) diff --git a/examples/src/expects/mod.rs b/examples/src/expects/mod.rs index 6714d30..fbca25f 100644 --- a/examples/src/expects/mod.rs +++ b/examples/src/expects/mod.rs @@ -7,7 +7,7 @@ mod tests { use xcm::latest::prelude::*; use xcm_simulator::TestExt; - const AMOUNT: u128 = 10; + const AMOUNT: u128 = 50 * CENTS; const QUERY_ID: u64 = 1234; /// Scenario: @@ -19,10 +19,16 @@ mod tests { #[test] fn expect_asset() { MockNet::reset(); + + let message_fee = relay_chain::estimate_message_fee(5); + ParaA::execute_with(|| { let message = Xcm(vec![ - WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset((Here, AMOUNT + message_fee).into()), + BuyExecution { + fees: (Here, message_fee).into(), + weight_limit: WeightLimit::Unlimited, + }, // Set the instructions that are executed when ExpectAsset does not pass. // In this case, reporting back an error to the Parachain. SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { @@ -30,19 +36,24 @@ mod tests { query_id: QUERY_ID, max_weight: Weight::from_all(0), })])), - ExpectAsset((Here, AMOUNT + 10).into()), + ExpectAsset((Here, AMOUNT + 10 * CENTS).into()), // Add Instructions that do something with assets in holding when ExpectAsset passes. ]); assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); }); + let instruction_index_that_errored = 3; + // Check that QueryResponse message with ExpectationFalse error was received. ParaA::execute_with(|| { assert_eq!( parachain::MsgQueue::received_dmp(), vec![Xcm(vec![QueryResponse { query_id: QUERY_ID, - response: Response::ExecutionResult(Some((3, XcmError::ExpectationFalse))), + response: Response::ExecutionResult(Some(( + instruction_index_that_errored, + XcmError::ExpectationFalse + ))), max_weight: Weight::from_all(0), querier: Some(Here.into()), }])], @@ -58,10 +69,15 @@ mod tests { fn expect_origin() { MockNet::reset(); + let message_fee = relay_chain::estimate_message_fee(6); + ParaA::execute_with(|| { let message = Xcm(vec![ - WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset((Here, AMOUNT + message_fee).into()), + BuyExecution { + fees: (Here, message_fee).into(), + weight_limit: WeightLimit::Unlimited, + }, // Set the instructions that are executed when ExpectOrigin does not pass. // In this case, reporting back an error to the Parachain. SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { @@ -76,13 +92,18 @@ mod tests { assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); }); + let instruction_index_that_errored = 4; + // Check that QueryResponse message with ExpectationFalse error was received. ParaA::execute_with(|| { assert_eq!( parachain::MsgQueue::received_dmp(), vec![Xcm(vec![QueryResponse { query_id: QUERY_ID, - response: Response::ExecutionResult(Some((4, XcmError::ExpectationFalse))), + response: Response::ExecutionResult(Some(( + instruction_index_that_errored, + XcmError::ExpectationFalse + ))), max_weight: Weight::from_all(0), querier: None, }])], @@ -98,10 +119,15 @@ mod tests { fn expect_pallet() { MockNet::reset(); + let message_fee = relay_chain::estimate_message_fee(5); + ParaA::execute_with(|| { let message = Xcm(vec![ - WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset((Here, message_fee).into()), + BuyExecution { + fees: (Here, message_fee).into(), + weight_limit: WeightLimit::Unlimited, + }, // Set the instructions that are executed when ExpectPallet does not pass. // In this case, reporting back an error to the Parachain. SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { @@ -147,10 +173,15 @@ mod tests { fn expect_error() { MockNet::reset(); + let message_fee = relay_chain::estimate_message_fee(6); + ParaA::execute_with(|| { let message = Xcm(vec![ - WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset((Here, message_fee).into()), + BuyExecution { + fees: (Here, message_fee).into(), + weight_limit: WeightLimit::Unlimited, + }, // ReportError is only executed if the thrown error is the `VersionIncompatible` error. SetErrorHandler(Xcm(vec![ ExpectError(Some((1, XcmError::VersionIncompatible))), @@ -174,7 +205,7 @@ mod tests { // Does not receive a message as the incorrect error was thrown during execution. ParaA::execute_with(|| { - assert_eq!(parachain::MsgQueue::received_dmp(), vec![],); + assert_eq!(parachain::MsgQueue::received_dmp(), vec![]); }); } @@ -189,6 +220,7 @@ mod tests { #[test] fn expect_transact_status() { MockNet::reset(); + // Runtime call dispatched by the Transact instruction. // force_set_balance requires root origin. let call = relay_chain::RuntimeCall::Balances(pallet_balances::Call::< @@ -198,9 +230,15 @@ mod tests { new_free: 100, }); + let message_fee = relay_chain::estimate_message_fee(6); + let set_balance_weight_estimation = Weight::from_parts(1_000_000_000, 10_000); + let set_balance_fee_estimation = + relay_chain::estimate_fee_for_weight(set_balance_weight_estimation); + let fees = message_fee + set_balance_fee_estimation; + let message = Xcm(vec![ - WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset((Here, fees).into()), + BuyExecution { fees: (Here, fees).into(), weight_limit: WeightLimit::Unlimited }, SetErrorHandler(Xcm(vec![ReportTransactStatus(QueryResponseInfo { destination: Parachain(1).into(), query_id: QUERY_ID, @@ -208,14 +246,14 @@ mod tests { })])), Transact { origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + require_weight_at_most: set_balance_weight_estimation, call: call.encode().into(), }, ExpectTransactStatus(MaybeErrorCode::Success), ]); ParaA::execute_with(|| { - assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); }); // The execution of force_set_balance does not succeed, and error is reported back to the parachain. diff --git a/examples/src/first_look.rs b/examples/src/first_look.rs index a7bbe2d..31362bd 100644 --- a/examples/src/first_look.rs +++ b/examples/src/first_look.rs @@ -12,14 +12,16 @@ mod tests { ParaA::execute_with(|| { // Amount to transfer. - let amount: u128 = 10; + let amount: u128 = 10 * CENTS; // Check that the balance of Alice is equal to the `INITIAL_BALANCE`. assert_eq!(ParachainBalances::free_balance(&ALICE), INITIAL_BALANCE); + let fee = parachain::estimate_message_fee(3); + // The XCM used to transfer funds from Alice to Bob. let message = Xcm(vec![ - WithdrawAsset((Here, amount).into()), - BuyExecution { fees: (Here, amount).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset(vec![(Here, amount).into(), (Parent, fee).into()].into()), + BuyExecution { fees: (Parent, fee).into(), weight_limit: WeightLimit::Unlimited }, DepositAsset { assets: All.into(), beneficiary: MultiLocation { @@ -36,11 +38,12 @@ mod tests { assert_ok!(ParachainPalletXcm::execute( parachain::RuntimeOrigin::signed(ALICE), Box::new(xcm::VersionedXcm::from(message.clone())), - 10.into() + (100_000_000_000, 100_000_000_000).into() )); // Check if the funds are subtracted from the account of Alice and added to the account of Bob. assert_eq!(ParachainBalances::free_balance(ALICE), INITIAL_BALANCE - amount); + assert_eq!(parachain::Assets::balance(0, ALICE), INITIAL_BALANCE - fee); assert_eq!(ParachainBalances::free_balance(BOB), amount); }); } diff --git a/examples/src/holding_modifiers/mod.rs b/examples/src/holding_modifiers/mod.rs index e22d329..1b05b76 100644 --- a/examples/src/holding_modifiers/mod.rs +++ b/examples/src/holding_modifiers/mod.rs @@ -13,8 +13,8 @@ mod tests { #[test] fn burn_assets() { let message = Xcm(vec![ + UnpaidExecution { weight_limit: WeightLimit::Unlimited, check_origin: None }, WithdrawAsset((Here, 10 * CENTS).into()), - BuyExecution { fees: (Here, CENTS).into(), weight_limit: WeightLimit::Unlimited }, BurnAsset((Here, 4 * CENTS).into()), ReportHolding { response_info: QueryResponseInfo { @@ -58,8 +58,8 @@ mod tests { parachain::set_exchange_assets(assets_in_exchange); let message = Xcm(vec![ + UnpaidExecution { weight_limit: WeightLimit::Unlimited, check_origin: None }, WithdrawAsset((Here, 10 * CENTS).into()), - BuyExecution { fees: (Here, CENTS).into(), weight_limit: WeightLimit::Unlimited }, // Maximal field set to true. ExchangeAsset { give: Definite((Here, 5 * CENTS).into()), @@ -82,7 +82,7 @@ mod tests { ParaA::execute_with(|| { assert_eq!(parachain::exchange_assets(), vec![(Here, 5 * CENTS).into()].into()); - assert_eq!(ParachainAssets::balance(1u128, &ALICE), INITIAL_BALANCE + 10 * CENTS); + assert_eq!(ParachainAssets::balance(0, &ALICE), INITIAL_BALANCE + 10 * CENTS); assert_eq!(ParachainBalances::free_balance(ALICE), INITIAL_BALANCE + 5 * CENTS); }) } @@ -103,8 +103,8 @@ mod tests { parachain::set_exchange_assets(assets_in_exchange); let message = Xcm(vec![ + UnpaidExecution { weight_limit: WeightLimit::Unlimited, check_origin: None }, WithdrawAsset((Here, 10 * CENTS).into()), - BuyExecution { fees: (Here, CENTS).into(), weight_limit: WeightLimit::Unlimited }, // Maximal field set to false. ExchangeAsset { give: Definite((Here, 5 * CENTS).into()), @@ -130,7 +130,7 @@ mod tests { parachain::exchange_assets(), vec![(Parent, 5 * CENTS).into(), (Here, 5 * CENTS).into()].into() ); - assert_eq!(ParachainAssets::balance(1u128, &ALICE), INITIAL_BALANCE + 5 * CENTS); + assert_eq!(ParachainAssets::balance(0, &ALICE), INITIAL_BALANCE + 5 * CENTS); assert_eq!(ParachainBalances::free_balance(ALICE), INITIAL_BALANCE + 5 * CENTS); }) } diff --git a/examples/src/locks/mod.rs b/examples/src/locks/mod.rs index 6d1d404..2c67555 100644 --- a/examples/src/locks/mod.rs +++ b/examples/src/locks/mod.rs @@ -1,29 +1,34 @@ #[cfg(test)] mod tests { use crate::simple_test_net::*; - use frame_support::{assert_ok, pallet_prelude::Weight}; + use frame_support::assert_ok; use pallet_balances::{BalanceLock, Reasons}; use xcm::latest::prelude::*; use xcm_simulator::TestExt; /// Scenario: - /// Parachain A locks 5 Cents of relay chain native assets of its Sovereign account on the relay chain and assigns Parachain B as unlocker. + /// ALICE from parachain A locks 5 cents of relay chain native assets of its Sovereign account on the relay chain and assigns Parachain B as unlocker. /// Parachain A then asks Parachain B to unlock the funds partly. Parachain B responds by sending an UnlockAssets instruction to the relay chain. + #[ignore] // TODO: Fix issue upstream #[test] fn remote_locking_on_relay() { MockNet::reset(); + let fee = relay_chain::estimate_message_fee(4); // Accounts for the `DescendOrigin` instruction added by `send_xcm` + ParaA::execute_with(|| { - let message = Xcm(vec![LockAsset { - asset: (Here, 5 * CENTS).into(), - unlocker: (Parachain(2)).into(), - }]); - assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); + let message = Xcm(vec![ + WithdrawAsset((Here, fee).into()), + BuyExecution { fees: (Here, fee).into(), weight_limit: WeightLimit::Unlimited }, + LockAsset { asset: (Here, 5 * CENTS).into(), unlocker: (Parachain(2)).into() }, + ]); + let interior = AccountId32 { id: ALICE.into(), network: None }; + assert_ok!(ParachainPalletXcm::send_xcm(interior, Parent, message.clone())); }); Relay::execute_with(|| { assert_eq!( - relay_chain::Balances::locks(¶chain_sovereign_account_id(1)), + relay_chain::Balances::locks(¶chain_account_sovereign_account_id(1, ALICE)), vec![BalanceLock { id: *b"py/xcmlk", amount: 5 * CENTS, reasons: Reasons::All }] ); }); @@ -32,24 +37,30 @@ mod tests { assert_eq!( parachain::MsgQueue::received_dmp(), vec![Xcm(vec![NoteUnlockable { - owner: (Parent, Parachain(1)).into(), + owner: (Parent, Parachain(1), AccountId32 { id: ALICE.into(), network: None }) + .into(), asset: (Parent, 5 * CENTS).into() }])] ); }); ParaA::execute_with(|| { - let message = Xcm(vec![RequestUnlock { - asset: (Parent, 3 * CENTS).into(), - locker: Parent.into(), - }]); - - assert_ok!(ParachainPalletXcm::send_xcm(Here, (Parent, Parachain(2)), message.clone())); + let message = Xcm(vec![ + WithdrawAsset((Parent, fee).into()), + BuyExecution { fees: (Parent, fee).into(), weight_limit: WeightLimit::Unlimited }, + RequestUnlock { asset: (Parent, 3 * CENTS).into(), locker: Parent.into() }, + ]); + let interior = AccountId32 { id: ALICE.into(), network: None }; + assert_ok!(ParachainPalletXcm::send_xcm( + interior, + (Parent, Parachain(2)), + message.clone() + )); }); Relay::execute_with(|| { assert_eq!( - relay_chain::Balances::locks(¶chain_sovereign_account_id(1)), + relay_chain::Balances::locks(¶chain_account_sovereign_account_id(1, ALICE)), vec![BalanceLock { id: *b"py/xcmlk", amount: 2 * CENTS, reasons: Reasons::All }] ); }); @@ -69,17 +80,23 @@ mod tests { /// Unlockers: B, C; Funds registered in pallet-xcm: 2, 5. /// Lock set in pallet-balances: 5. /// + #[ignore] // TODO: Fix issue upstream #[test] fn locking_overlap() { MockNet::reset(); + let fee = relay_chain::estimate_message_fee(4); + // 1) ParaA::execute_with(|| { let message = Xcm(vec![ + WithdrawAsset((Here, fee).into()), + BuyExecution { fees: (Here, fee).into(), weight_limit: WeightLimit::Unlimited }, LockAsset { asset: (Here, 10 * CENTS).into(), unlocker: (Parachain(2)).into() }, LockAsset { asset: (Here, 5 * CENTS).into(), unlocker: (Parachain(3)).into() }, ]); - assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); + let interior = AccountId32 { id: ALICE.into(), network: None }; + assert_ok!(ParachainPalletXcm::send_xcm(interior, Parent, message.clone())); }); Relay::execute_with(|| { @@ -94,7 +111,8 @@ mod tests { assert_eq!( parachain::MsgQueue::received_dmp(), vec![Xcm(vec![NoteUnlockable { - owner: (Parent, Parachain(1)).into(), + owner: (Parent, Parachain(1), AccountId32 { id: ALICE.into(), network: None }) + .into(), asset: (Parent, 10 * CENTS).into() }])] ); @@ -104,20 +122,31 @@ mod tests { assert_eq!( parachain::MsgQueue::received_dmp(), vec![Xcm(vec![NoteUnlockable { - owner: (Parent, Parachain(1)).into(), + owner: (Parent, Parachain(1), AccountId32 { id: ALICE.into(), network: None }) + .into(), asset: (Parent, 5 * CENTS).into() }])] ); }); + let new_fee = parachain::estimate_message_fee(3); + // 3) ParaA::execute_with(|| { - let message = Xcm(vec![RequestUnlock { - asset: (Parent, 8 * CENTS).into(), - locker: Parent.into(), - }]); - - assert_ok!(ParachainPalletXcm::send_xcm(Here, (Parent, Parachain(2)), message.clone())); + let message = Xcm(vec![ + WithdrawAsset((Parent, new_fee).into()), + BuyExecution { + fees: (Parent, new_fee).into(), + weight_limit: WeightLimit::Unlimited, + }, + RequestUnlock { asset: (Parent, 8 * CENTS).into(), locker: Parent.into() }, + ]); + let interior = AccountId32 { id: ALICE.into(), network: None }; + assert_ok!(ParachainPalletXcm::send_xcm( + interior, + (Parent, Parachain(2)), + message.clone() + )); }); // 4) diff --git a/examples/src/queries/mod.rs b/examples/src/queries/mod.rs index 96004df..d99626f 100644 --- a/examples/src/queries/mod.rs +++ b/examples/src/queries/mod.rs @@ -7,7 +7,7 @@ mod tests { use xcm::latest::prelude::*; use xcm_simulator::TestExt; - const AMOUNT: u128 = 1 * CENTS; + const AMOUNT: u128 = 50 * CENTS; /// Arbitrary query id const QUERY_ID: u64 = 1234; @@ -20,14 +20,16 @@ mod tests { fn query_holding() { MockNet::reset(); - // Send a message which fully succeeds to the relay chain. + let fee_in_relay = relay_chain::estimate_message_fee(4); + + // Send a message which succeeds to the relay chain. // And then report the status of the holding register back to ParaA ParaA::execute_with(|| { let message = Xcm(vec![ WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: Unlimited }, + BuyExecution { fees: (Here, fee_in_relay).into(), weight_limit: Unlimited }, DepositAsset { - assets: Definite((Here, AMOUNT - 5).into()), + assets: Definite((Here, AMOUNT - (5 * CENTS)).into()), beneficiary: Parachain(2).into(), }, ReportHolding { @@ -53,7 +55,7 @@ mod tests { // Deposit executed assert_eq!( relay_chain::Balances::free_balance(parachain_sovereign_account_id(2)), - INITIAL_BALANCE + AMOUNT - 5 + INITIAL_BALANCE + (AMOUNT - 5 * CENTS) ); }); @@ -63,7 +65,9 @@ mod tests { parachain::MsgQueue::received_dmp(), vec![Xcm(vec![QueryResponse { query_id: QUERY_ID, - response: Response::Assets((Parent, AMOUNT - (AMOUNT - 5)).into()), + response: Response::Assets( + (Parent, AMOUNT - (AMOUNT - 5 * CENTS) - fee_in_relay).into() + ), max_weight: Weight::from_all(0), querier: Some(Here.into()), }])], @@ -72,7 +76,7 @@ mod tests { } /// Scenario: - /// Parachain A wants to query the `PalletInfo` of the balances pallet in the relay chain. + /// Parachain A wants to query for information on the balances pallet in the relay chain. /// It sends a `QueryPallet` instruction to the relay chain. /// The relay chain responds with a `QueryResponse` instruction containing the `PalletInfo`. /// @@ -81,15 +85,24 @@ mod tests { fn query_pallet() { MockNet::reset(); + let fee_in_relay = relay_chain::estimate_message_fee(3); + ParaA::execute_with(|| { - let message = Xcm(vec![QueryPallet { - module_name: "pallet_balances".into(), - response_info: QueryResponseInfo { - destination: Parachain(1).into(), - query_id: QUERY_ID, - max_weight: Weight::from_all(0), + let message = Xcm(vec![ + WithdrawAsset((Here, fee_in_relay).into()), + BuyExecution { + fees: (Here, fee_in_relay).into(), + weight_limit: WeightLimit::Unlimited, }, - }]); + QueryPallet { + module_name: "pallet_balances".into(), + response_info: QueryResponseInfo { + destination: Parachain(1).into(), + query_id: QUERY_ID, + max_weight: Weight::from_all(0), + }, + }, + ]); assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); print_para_events(); @@ -121,26 +134,36 @@ mod tests { fn report_error() { MockNet::reset(); + let fee_in_relay = + relay_chain::estimate_message_fee(4) + relay_chain::estimate_message_fee(1); + let message = Xcm(vec![ + WithdrawAsset((Here, fee_in_relay).into()), + BuyExecution { + fees: (Here, fee_in_relay).into(), + weight_limit: WeightLimit::Unlimited, + }, // Set the Error Handler to report back status of Error register. SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { destination: Parachain(1).into(), query_id: QUERY_ID, max_weight: Weight::from_all(0), })])), - Trap(1u64), + Trap(1u64), // Error is thrown on index 3 ]); ParaA::execute_with(|| { assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); }); + let index_of_error = 3; + ParaA::execute_with(|| { assert_eq!( parachain::MsgQueue::received_dmp(), vec![Xcm(vec![QueryResponse { query_id: QUERY_ID, - response: Response::ExecutionResult(Some((1, XcmError::Trap(1)))), + response: Response::ExecutionResult(Some((index_of_error, XcmError::Trap(1)))), max_weight: Weight::from_all(0), querier: Some(Here.into()), }])], @@ -162,10 +185,19 @@ mod tests { }, ); + let message_fee = relay_chain::estimate_message_fee(5); + let remark_weight_estimation = Weight::from_parts(20_000_000, 100_000); // We overestimate the weight taken by this extrinsic + let remark_fee_estimation = relay_chain::estimate_fee_for_weight(remark_weight_estimation); + let message = Xcm(vec![ + WithdrawAsset((Here, message_fee + remark_fee_estimation).into()), + BuyExecution { + fees: (Here, message_fee + remark_fee_estimation).into(), + weight_limit: WeightLimit::Unlimited, + }, Transact { origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + require_weight_at_most: remark_weight_estimation, call: call.encode().into(), }, ReportTransactStatus(QueryResponseInfo { diff --git a/examples/src/simple_test_net/mod.rs b/examples/src/simple_test_net/mod.rs index 8dc24b8..6696489 100644 --- a/examples/src/simple_test_net/mod.rs +++ b/examples/src/simple_test_net/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -17,9 +17,17 @@ pub mod parachain; pub mod relay_chain; -use frame_support::{assert_ok, sp_tracing, traits::GenesisBuild}; +use core::{borrow::Borrow, marker::PhantomData}; + +use frame_support::{ + ensure, + pallet_prelude::Weight, + sp_tracing, + traits::{GenesisBuild, ProcessMessageError}, +}; +use sp_core::blake2_256; use xcm::prelude::*; -use xcm_executor::traits::Convert; +use xcm_executor::traits::{Convert, ShouldExecute}; use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; // Accounts @@ -31,7 +39,7 @@ pub const BOB: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([2u8; 32]) pub type Balance = u128; pub const UNITS: Balance = 10_000_000_000; pub const CENTS: Balance = UNITS / 100; // 100_000_000 -pub const INITIAL_BALANCE: u128 = 1 * UNITS; +pub const INITIAL_BALANCE: u128 = 10 * UNITS; decl_test_parachain! { pub struct ParaA { @@ -56,7 +64,7 @@ decl_test_parachain! { Runtime = parachain::Runtime, XcmpMessageHandler = parachain::MsgQueue, DmpMessageHandler = parachain::MsgQueue, - new_ext = para_ext(3), + new_ext = para_ext(2), } } @@ -89,23 +97,86 @@ pub fn parachain_sovereign_account_id(para: u32) -> relay_chain::AccountId { relay_chain::SovereignAccountOf::convert(location.into()).unwrap() } +pub fn parachain_account_sovereign_account_id( + para: u32, + who: sp_runtime::AccountId32, +) -> relay_chain::AccountId { + let location = ( + Parachain(para), + AccountId32 { network: Some(relay_chain::RelayNetwork::get()), id: who.into() }, + ); + relay_chain::SovereignAccountOf::convert(location.into()).unwrap() +} + +pub fn sibling_sovereign_account_id(para: u32) -> parachain::AccountId { + let location = (Parent, Parachain(para)); + parachain::SovereignAccountOf::convert(location.into()).unwrap() +} + +pub fn sibling_account_sovereign_account_id( + para: u32, + who: sp_runtime::AccountId32, +) -> parachain::AccountId { + let location = (Parent, Parachain(para), AccountId32 { network: None, id: who.into() }); + parachain::SovereignAccountOf::convert(location.into()).unwrap() +} + +pub fn relay_account_sovereign_account_id(who: sp_runtime::AccountId32) -> parachain::AccountId { + let location = (Parent, AccountId32 { network: None, id: who.into() }); + parachain::SovereignAccountOf::convert(location.into()).unwrap() +} + pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { use parachain::{MsgQueue, Runtime, System}; let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let other_para_ids = match para_id { + 1 => [2, 3], + 2 => [1, 3], + 3 => [1, 2], + _ => panic!("No parachain exists with para_id = {para_id}"), + }; + pallet_balances::GenesisConfig:: { - balances: vec![(ALICE, INITIAL_BALANCE), (relay_sovereign_account_id(), INITIAL_BALANCE)], + balances: vec![(ALICE, INITIAL_BALANCE), (relay_sovereign_account_id(), INITIAL_BALANCE)] + .into_iter() + .chain(other_para_ids.iter().map( + // Initial balance of native token for ALICE on all sibling sovereign accounts + |¶_id| (sibling_account_sovereign_account_id(para_id, ALICE), INITIAL_BALANCE), + )) + .chain(other_para_ids.iter().map( + // Initial balance of native token all sibling sovereign accounts + |¶_id| (sibling_sovereign_account_id(para_id), INITIAL_BALANCE), + )) + .collect(), } .assimilate_storage(&mut t) .unwrap(); pallet_assets::GenesisConfig:: { assets: vec![ - (1u128, ADMIN, false, 1u128), // Create derivative asset for relay's native token - ], + (0u128, ADMIN, false, 1u128), // Create derivative asset for relay's native token + ] + .into_iter() + .chain(other_para_ids.iter().map(|¶_id| (para_id as u128, ADMIN, false, 1u128))) // Derivative assets for the other parachains' native tokens + .collect(), metadata: Default::default(), - accounts: vec![(1u128, ALICE, INITIAL_BALANCE)], + accounts: vec![ + (0u128, ALICE, INITIAL_BALANCE), + (0u128, relay_sovereign_account_id(), INITIAL_BALANCE), + ] + .into_iter() + .chain(other_para_ids.iter().map(|¶_id| (para_id as u128, ALICE, INITIAL_BALANCE))) // Initial balance for derivatives of other parachains' tokens + .chain(other_para_ids.iter().map(|¶_id| { + (0u128, sibling_account_sovereign_account_id(para_id, ALICE), INITIAL_BALANCE) + })) // Initial balance for sovereign accounts (for fee payment) + .chain( + other_para_ids + .iter() + .map(|¶_id| (0u128, sibling_sovereign_account_id(para_id), INITIAL_BALANCE)), + ) // Initial balance for sovereign accounts (for fee payment) + .collect(), } .assimilate_storage(&mut t) .unwrap(); @@ -129,6 +200,10 @@ pub fn relay_ext() -> sp_io::TestExternalities { (ALICE, INITIAL_BALANCE), (parachain_sovereign_account_id(1), INITIAL_BALANCE), (parachain_sovereign_account_id(2), INITIAL_BALANCE), + (parachain_sovereign_account_id(3), INITIAL_BALANCE), + (parachain_account_sovereign_account_id(1, ALICE), INITIAL_BALANCE), + (parachain_account_sovereign_account_id(2, ALICE), INITIAL_BALANCE), + (parachain_account_sovereign_account_id(3, ALICE), INITIAL_BALANCE), ], } .assimilate_storage(&mut t) @@ -156,3 +231,88 @@ pub type ParachainPalletXcm = pallet_xcm::Pallet; pub type RelaychainBalances = pallet_balances::Pallet; pub type ParachainBalances = pallet_balances::Pallet; pub type ParachainAssets = pallet_assets::Pallet; + +/// Prefix for generating alias account for accounts coming +/// from chains that use 32 byte long representations. +pub const FOREIGN_CHAIN_PREFIX_PARA_32: [u8; 37] = *b"ForeignChainAliasAccountPrefix_Para32"; + +/// Prefix for generating alias account for accounts coming +/// from the relay chain using 32 byte long representations. +pub const FOREIGN_CHAIN_PREFIX_RELAY: [u8; 36] = *b"ForeignChainAliasAccountPrefix_Relay"; + +pub struct ForeignChainAliasAccount(PhantomData); +impl + Clone> Convert + for ForeignChainAliasAccount +{ + fn convert_ref(location: impl Borrow) -> Result { + let entropy = match location.borrow() { + // Used on the relay chain for sending paras that use 32 byte accounts + MultiLocation { + parents: 0, + interior: X2(Parachain(para_id), AccountId32 { id, .. }), + } => ForeignChainAliasAccount::::from_para_32(para_id, id, 0), + + // Used on para-chain for sending paras that use 32 byte accounts + MultiLocation { + parents: 1, + interior: X2(Parachain(para_id), AccountId32 { id, .. }), + } => ForeignChainAliasAccount::::from_para_32(para_id, id, 1), + + // Used on para-chain for sending from the relay chain + MultiLocation { parents: 1, interior: X1(AccountId32 { id, .. }) } => + ForeignChainAliasAccount::::from_relay_32(id, 1), + + // No other conversions provided + _ => return Err(()), + }; + + Ok(entropy.into()) + } + + fn reverse_ref(_: impl Borrow) -> Result { + Err(()) + } +} + +impl ForeignChainAliasAccount { + fn from_para_32(para_id: &u32, id: &[u8; 32], parents: u8) -> [u8; 32] { + (FOREIGN_CHAIN_PREFIX_PARA_32, para_id, id, parents).using_encoded(blake2_256) + } + + fn from_relay_32(id: &[u8; 32], parents: u8) -> [u8; 32] { + (FOREIGN_CHAIN_PREFIX_RELAY, id, parents).using_encoded(blake2_256) + } +} + +// TODO: Is this vulnerable to DoS? It's how the instructions work +pub struct AllowNoteUnlockables; +impl ShouldExecute for AllowNoteUnlockables { + fn should_execute( + _origin: &MultiLocation, + instructions: &mut [Instruction], + _max_weight: Weight, + _weight_credit: &mut Weight, + ) -> Result<(), ProcessMessageError> { + ensure!(instructions.len() == 1, ProcessMessageError::BadFormat); + match instructions.first() { + Some(NoteUnlockable { .. }) => Ok(()), + _ => Err(ProcessMessageError::BadFormat), + } + } +} + +pub struct AllowUnlocks; +impl ShouldExecute for AllowUnlocks { + fn should_execute( + _origin: &MultiLocation, + instructions: &mut [Instruction], + _max_weight: Weight, + _weight_credit: &mut Weight, + ) -> Result<(), ProcessMessageError> { + ensure!(instructions.len() == 1, ProcessMessageError::BadFormat); + match instructions.first() { + Some(UnlockAsset { .. }) => Ok(()), + _ => Err(ProcessMessageError::BadFormat), + } + } +} diff --git a/examples/src/simple_test_net/parachain.rs b/examples/src/simple_test_net/parachain.rs index 7891307..2274ccf 100644 --- a/examples/src/simple_test_net/parachain.rs +++ b/examples/src/simple_test_net/parachain.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -16,16 +16,19 @@ //! Parachain runtime mock. -use super::Balance; +use super::{AllowNoteUnlockables, Balance, ForeignChainAliasAccount}; use codec::{Decode, Encode}; use core::marker::PhantomData; use frame_support::{ construct_runtime, ensure, parameter_types, traits::{ - AsEnsureOriginWithArg, ContainsPair, EnsureOrigin, EnsureOriginWithArg, Everything, - EverythingBut, Nothing, + AsEnsureOriginWithArg, Contains, ContainsPair, EnsureOrigin, EnsureOriginWithArg, + Everything, EverythingBut, Nothing, + }, + weights::{ + constants::{WEIGHT_PROOF_SIZE_PER_MB, WEIGHT_REF_TIME_PER_SECOND}, + Weight, }, - weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, }; use frame_system::{EnsureRoot, EnsureSigned}; use pallet_xcm::XcmPassthrough; @@ -43,11 +46,12 @@ use sp_runtime::{ use sp_std::{cell::RefCell, prelude::*}; use xcm::{latest::prelude::*, VersionedXcm}; use xcm_builder::{ - AccountId32Aliases, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, Case, - ConvertedConcreteId, CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, - FixedRateOfFungible, FixedWeightBounds, FungiblesAdapter, IsConcrete, NativeAsset, NoChecking, - NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, + AsPrefixedGeneralIndex, ConvertedConcreteId, CurrencyAdapter as XcmCurrencyAdapter, + EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, FungiblesAdapter, IsConcrete, + NativeAsset, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, WithComputedOrigin, }; use xcm_executor::{ traits::{AssetExchange, Convert, JustTry}, @@ -58,6 +62,7 @@ pub type AccountId = AccountId32; pub type AssetIdForAssets = u128; pub type SovereignAccountOf = ( + ForeignChainAliasAccount, SiblingParachainConvertsVia, AccountId32Aliases, ParentIsPreset, @@ -211,8 +216,8 @@ pub type XcmOriginToCallOrigin = ( ); parameter_types! { - pub const UnitWeightCost: Weight = Weight::from_parts(1, 1); - pub RatePerSecondPerByte: (AssetId, u128, u128) = (Concrete(Parent.into()), 1, 1); + pub const XcmInstructionWeight: Weight = Weight::from_parts(1_000, 1_000); + pub TokensPerSecondPerMegabyte: (AssetId, u128, u128) = (Concrete(Parent.into()), 1_000_000_000_000, 1024 * 1024); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; pub ForeignPrefix: MultiLocation = (Parent,).into(); @@ -221,18 +226,35 @@ parameter_types! { (Parent.into(), Wild(AllOf { id: Concrete(Parent.into()), fun: WildFungible })); } +pub fn estimate_message_fee(number_of_instructions: u64) -> u128 { + let weight = estimate_weight(number_of_instructions); + + estimate_fee_for_weight(weight) +} + +pub fn estimate_weight(number_of_instructions: u64) -> Weight { + XcmInstructionWeight::get().saturating_mul(number_of_instructions) +} + +pub fn estimate_fee_for_weight(weight: Weight) -> u128 { + let (_, units_per_second, units_per_mb) = TokensPerSecondPerMegabyte::get(); + + units_per_second * (weight.ref_time() as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128) + + units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128) +} + pub type LocalBalancesTransactor = XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; -pub struct FromNativeAssetToFungible( - core::marker::PhantomData<(MultiLocation, AssetId)>, -); +pub struct FromMultiLocationToAsset(PhantomData<(MultiLocation, AssetId)>); impl Convert - for FromNativeAssetToFungible + for FromMultiLocationToAsset { fn convert(value: MultiLocation) -> Result { match value { - MultiLocation { parents: 1, interior: Here } => Ok(1 as AssetIdForAssets), + MultiLocation { parents: 1, interior: Here } => Ok(0 as AssetIdForAssets), + MultiLocation { parents: 1, interior: X1(Parachain(para_id)) } => + Ok(para_id as AssetIdForAssets), _ => Err(value), } } @@ -243,7 +265,7 @@ pub type ForeignAssetsTransactor = FungiblesAdapter< ConvertedConcreteId< AssetIdForAssets, Balance, - FromNativeAssetToFungible, + FromMultiLocationToAsset, JustTry, >, SovereignAccountOf, @@ -265,8 +287,23 @@ pub type ForeignUniquesTransactor = NonFungiblesAdapter< pub type AssetTransactors = (LocalBalancesTransactor, ForeignAssetsTransactor, ForeignUniquesTransactor); +pub struct ParentRelay; +impl Contains for ParentRelay { + fn contains(location: &MultiLocation) -> bool { + location.contains_parents_only(1) + } +} + pub type XcmRouter = super::ParachainXcmRouter; -pub type Barrier = AllowUnpaidExecutionFrom; +pub type Barrier = WithComputedOrigin< + ( + AllowNoteUnlockables, + AllowExplicitUnpaidExecutionFrom, + AllowTopLevelPaidExecutionFrom, + ), + UniversalLocation, + ConstU32<1>, +>; parameter_types! { pub NftCollectionOne: MultiAssetFilter @@ -324,8 +361,8 @@ impl Config for XcmConfig { type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; type Barrier = Barrier; - type Weigher = FixedWeightBounds; - type Trader = FixedRateOfFungible; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfFungible; type ResponseHandler = (); type AssetTrap = PolkadotXcm; type AssetLocker = PolkadotXcm; @@ -512,7 +549,7 @@ impl pallet_xcm::Config for Runtime { type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Nothing; type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type UniversalLocation = UniversalLocation; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; diff --git a/examples/src/simple_test_net/relay_chain.rs b/examples/src/simple_test_net/relay_chain.rs index 8b8129a..eccf084 100644 --- a/examples/src/simple_test_net/relay_chain.rs +++ b/examples/src/simple_test_net/relay_chain.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -18,8 +18,11 @@ use frame_support::{ construct_runtime, parameter_types, - traits::{AsEnsureOriginWithArg, Everything, Nothing}, - weights::Weight, + traits::{AsEnsureOriginWithArg, Contains, Everything, Nothing}, + weights::{ + constants::{WEIGHT_PROOF_SIZE_PER_MB, WEIGHT_REF_TIME_PER_SECOND}, + Weight, + }, }; use frame_system::EnsureRoot; @@ -30,15 +33,16 @@ use polkadot_parachain::primitives::Id as ParaId; use polkadot_runtime_parachains::{configuration, origin, shared, ump}; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, ChildParachainAsNative, - ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, ConvertedConcreteId, - CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete, - NoChecking, NonFungiblesAdapter, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, + AsPrefixedGeneralIndex, ChildParachainAsNative, ChildParachainConvertsVia, + ChildSystemParachainAsSuperuser, ConvertedConcreteId, CurrencyAdapter as XcmCurrencyAdapter, + FixedRateOfFungible, FixedWeightBounds, IsConcrete, NoChecking, NonFungiblesAdapter, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, WithComputedOrigin, }; use xcm_executor::{traits::JustTry, Config, XcmExecutor}; -use super::Balance; +use super::{AllowNoteUnlockables, AllowUnlocks, Balance, ForeignChainAliasAccount}; pub type AccountId = AccountId32; @@ -130,6 +134,7 @@ parameter_types! { } pub type SovereignAccountOf = ( + ForeignChainAliasAccount, AccountId32Aliases, ChildParachainConvertsVia, SiblingParachainConvertsVia, @@ -157,15 +162,48 @@ type LocalOriginConverter = ( ); parameter_types! { - pub const BaseXcmWeight: Weight = Weight::from_parts(1_000, 1_000); - pub TokensPerSecondPerByte: (AssetId, u128, u128) = + pub const XcmInstructionWeight: Weight = Weight::from_parts(1_000, 1_000); + pub TokensPerSecondPerMegabyte: (AssetId, u128, u128) = (Concrete(TokenLocation::get()), 1_000_000_000_000, 1024 * 1024); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; } +pub fn estimate_message_fee(number_of_instructions: u64) -> u128 { + let weight = estimate_message_weight(number_of_instructions); + + estimate_fee_for_weight(weight) +} + +pub fn estimate_message_weight(number_of_instructions: u64) -> Weight { + XcmInstructionWeight::get().saturating_mul(number_of_instructions) +} + +pub fn estimate_fee_for_weight(weight: Weight) -> u128 { + let (_, units_per_second, units_per_mb) = TokensPerSecondPerMegabyte::get(); + + units_per_second * (weight.ref_time() as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128) + + units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128) +} + +pub struct ChildrenParachains; +impl Contains for ChildrenParachains { + fn contains(location: &MultiLocation) -> bool { + matches!(location, MultiLocation { parents: 0, interior: X1(Parachain(_)) }) + } +} + pub type XcmRouter = super::RelayChainXcmRouter; -pub type Barrier = AllowUnpaidExecutionFrom; +pub type Barrier = WithComputedOrigin< + ( + AllowNoteUnlockables, + AllowUnlocks, + AllowExplicitUnpaidExecutionFrom, + AllowTopLevelPaidExecutionFrom, + ), + UniversalLocation, + ConstU32<1>, +>; pub struct XcmConfig; impl Config for XcmConfig { @@ -177,8 +215,8 @@ impl Config for XcmConfig { type IsTeleporter = (); type UniversalLocation = UniversalLocation; type Barrier = Barrier; - type Weigher = FixedWeightBounds; - type Trader = FixedRateOfFungible; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfFungible; type ResponseHandler = XcmPallet; type AssetTrap = XcmPallet; type AssetLocker = XcmPallet; @@ -211,7 +249,7 @@ impl pallet_xcm::Config for Runtime { type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type UniversalLocation = UniversalLocation; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; diff --git a/examples/src/transact/mod.rs b/examples/src/transact/mod.rs index d767570..1fb6dc6 100644 --- a/examples/src/transact/mod.rs +++ b/examples/src/transact/mod.rs @@ -6,8 +6,6 @@ mod tests { use xcm::latest::prelude::*; use xcm_simulator::TestExt; - const AMOUNT: u128 = 1 * CENTS; - /// Scenario: /// Relay chain sets the balance of Alice on Parachain(1). /// The relay chain is able to do this, because Parachain(1) trusts the relay chain to execute runtime calls as root. @@ -19,16 +17,22 @@ mod tests { let call = parachain::RuntimeCall::Balances( pallet_balances::Call::::force_set_balance { who: ALICE, - new_free: 5 * AMOUNT, + new_free: 5 * CENTS, }, ); + let message_fee = parachain::estimate_message_fee(3); + let set_balance_weight_estimation = Weight::from_parts(1_000_000_000, 10_000); + let set_balance_fee_estimation = + parachain::estimate_fee_for_weight(set_balance_weight_estimation); + let fees = message_fee + set_balance_fee_estimation; + let message = Xcm(vec![ - WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset((Parent, fees).into()), + BuyExecution { fees: (Parent, fees).into(), weight_limit: WeightLimit::Unlimited }, Transact { origin_kind: OriginKind::Superuser, - require_weight_at_most: Weight::from_parts(1_000_000_000, 1024 * 1024), + require_weight_at_most: set_balance_weight_estimation, call: call.encode().into(), }, ]); @@ -38,7 +42,7 @@ mod tests { }); ParaA::execute_with(|| { - assert_eq!(ParachainBalances::free_balance(ALICE), 5 * AMOUNT); + assert_eq!(ParachainBalances::free_balance(ALICE), 5 * CENTS); }) } @@ -49,6 +53,7 @@ mod tests { #[test] fn transact_mint_nft() { MockNet::reset(); + let create_collection = relay_chain::RuntimeCall::Uniques(pallet_uniques::Call::< relay_chain::Runtime, >::create { @@ -56,6 +61,15 @@ mod tests { admin: parachain_sovereign_account_id(1), }); + let message_fee = relay_chain::estimate_message_fee(4); + let create_collection_weight_estimation = Weight::from_parts(1_000_000_000, 10_000); + let create_collection_fee_estimation = + relay_chain::estimate_fee_for_weight(create_collection_weight_estimation); + let mint_nft_weight_estimation = Weight::from_parts(1_000_000_000, 10_000); + let mint_nft_fee_estimation = + relay_chain::estimate_fee_for_weight(mint_nft_weight_estimation); + let fees = message_fee + create_collection_fee_estimation + mint_nft_fee_estimation; + let mint = relay_chain::RuntimeCall::Uniques(pallet_uniques::Call::::mint { collection: 1u32, @@ -64,16 +78,16 @@ mod tests { }); let message = Xcm(vec![ - WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset((Here, fees).into()), + BuyExecution { fees: (Here, fees).into(), weight_limit: WeightLimit::Unlimited }, Transact { origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + require_weight_at_most: create_collection_weight_estimation, call: create_collection.encode().into(), }, Transact { origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + require_weight_at_most: mint_nft_weight_estimation, call: mint.encode().into(), }, ]); diff --git a/examples/src/transfers/reserve.rs b/examples/src/transfers/reserve.rs index 36bd7ac..2e986ab 100644 --- a/examples/src/transfers/reserve.rs +++ b/examples/src/transfers/reserve.rs @@ -6,36 +6,58 @@ mod tests { use xcm_simulator::TestExt; /// Scenario: - /// ALICE transfers her FDOT from parachain A to parachain B. + /// ALICE transfers relay native tokens from parachain A to parachain B. #[test] fn reserve_backed_transfer_para_to_para() { MockNet::reset(); let withdraw_amount = 50 * CENTS; + // Estimated from the number of instructions and knowledge of the config + let fee_in_source = parachain::estimate_message_fee(3); + let fee_in_relay = relay_chain::estimate_message_fee(4); + let fee_in_destination = parachain::estimate_message_fee(4); + + // In this case, we know exactly how much fees we need for each step of the process let message: Xcm = Xcm(vec![ - WithdrawAsset((Parent, withdraw_amount).into()), + WithdrawAsset((Parent, withdraw_amount).into()), // Fees are paid in the relay's token + BuyExecution { + fees: (Parent, fee_in_source).into(), + weight_limit: WeightLimit::Unlimited, + }, InitiateReserveWithdraw { assets: All.into(), reserve: Parent.into(), xcm: Xcm(vec![ BuyExecution { - fees: (Here, withdraw_amount).into(), + fees: (Here, fee_in_relay).into(), weight_limit: WeightLimit::Unlimited, }, DepositReserveAsset { assets: All.into(), dest: Parachain(2).into(), - xcm: Xcm(vec![DepositAsset { - assets: All.into(), - beneficiary: Junction::AccountId32 { id: ALICE.into(), network: None } + xcm: Xcm(vec![ + BuyExecution { + fees: (Parent, fee_in_destination).into(), + weight_limit: WeightLimit::Unlimited, + }, + DepositAsset { + assets: All.into(), + beneficiary: Junction::AccountId32 { + id: ALICE.into(), + network: None, + } .into(), - }]), + }, + ]), }, ]), }, ]); + let fee_until_relay = fee_in_source + fee_in_relay; + let fee_until_destination = fee_until_relay + fee_in_destination; + ParaA::execute_with(|| { assert_ok!(parachain::PolkadotXcm::execute( parachain::RuntimeOrigin::signed(ALICE), @@ -43,43 +65,57 @@ mod tests { (100_000_000_000, 100_000_000_000).into(), )); - assert_eq!(parachain::Assets::balance(1, &ALICE), INITIAL_BALANCE - withdraw_amount); + assert_eq!(parachain::Assets::balance(0, &ALICE), INITIAL_BALANCE - withdraw_amount); }); Relay::execute_with(|| { assert_eq!( relay_chain::Balances::free_balance(¶chain_sovereign_account_id(2)), - INITIAL_BALANCE + withdraw_amount + INITIAL_BALANCE + withdraw_amount - fee_until_relay ); }); ParaB::execute_with(|| { - assert_eq!(parachain::Assets::balance(1, &ALICE), INITIAL_BALANCE + withdraw_amount); + assert_eq!( + parachain::Assets::balance(0, &ALICE), + INITIAL_BALANCE + withdraw_amount - fee_until_destination + ); }); } /// Scenario: - /// ALICE transfers her FDOT from relay to parachain B. + /// ALICE transfers relay native tokens from relay to parachain B. #[test] fn reserve_backed_transfer_relay_to_para() { MockNet::reset(); let withdraw_amount = 50 * CENTS; - let message: Xcm = Xcm(vec![TransferReserveAsset { - assets: (Here, withdraw_amount).into(), - dest: Parachain(2).into(), - xcm: Xcm(vec![ - BuyExecution { - fees: (Here, withdraw_amount).into(), - weight_limit: WeightLimit::Unlimited, - }, - DepositAsset { - assets: All.into(), - beneficiary: Junction::AccountId32 { id: ALICE.into(), network: None }.into(), - }, - ]), - }]); + let fee_in_source = relay_chain::estimate_message_fee(3); + let fee_in_destination = parachain::estimate_message_fee(4); + + let message: Xcm = Xcm(vec![ + WithdrawAsset((Here, fee_in_source).into()), + BuyExecution { + fees: (Here, fee_in_source).into(), + weight_limit: WeightLimit::Unlimited, + }, + TransferReserveAsset { + assets: (Here, withdraw_amount).into(), + dest: Parachain(2).into(), + xcm: Xcm(vec![ + BuyExecution { + fees: (Parent, fee_in_destination).into(), + weight_limit: WeightLimit::Unlimited, + }, + DepositAsset { + assets: All.into(), + beneficiary: Junction::AccountId32 { id: ALICE.into(), network: None } + .into(), + }, + ]), + }, + ]); Relay::execute_with(|| { assert_ok!(relay_chain::XcmPallet::execute( @@ -91,7 +127,7 @@ mod tests { // ALICE's balance in the relay chain decreases assert_eq!( relay_chain::Balances::free_balance(&ALICE), - INITIAL_BALANCE - withdraw_amount + INITIAL_BALANCE - withdraw_amount - fee_in_source ); // Parachain(2)'s sovereign account's balance increases @@ -102,7 +138,68 @@ mod tests { }); ParaB::execute_with(|| { - assert_eq!(parachain::Assets::balance(1, &ALICE), INITIAL_BALANCE + withdraw_amount); + assert_eq!( + parachain::Assets::balance(0, &ALICE), + INITIAL_BALANCE + (withdraw_amount - fee_in_destination) + ); + }); + } + + #[test] + fn reserve_backed_transfer_para_to_relay() { + MockNet::reset(); + + let withdraw_amount = 50 * CENTS; + + let fee_in_source = parachain::estimate_message_fee(3); + let fee_in_destination = relay_chain::estimate_message_fee(4); + + let message: Xcm = Xcm(vec![ + WithdrawAsset((Parent, withdraw_amount).into()), + BuyExecution { + fees: (Parent, fee_in_source).into(), + weight_limit: WeightLimit::Unlimited, + }, + InitiateReserveWithdraw { + assets: All.into(), + reserve: Parent.into(), + xcm: Xcm(vec![ + BuyExecution { + fees: (Here, fee_in_destination).into(), + weight_limit: WeightLimit::Unlimited, + }, + DepositAsset { + assets: All.into(), + beneficiary: Junction::AccountId32 { id: ALICE.into(), network: None } + .into(), + }, + ]), + }, + ]); + + ParaA::execute_with(|| { + assert_ok!(parachain::PolkadotXcm::execute( + parachain::RuntimeOrigin::signed(ALICE), + Box::new(xcm::VersionedXcm::V3(message.into())), + (100_000_000_000, 100_000_000_000).into(), + )); + + // ALICE's balance in the parachain decreases + assert_eq!(parachain::Assets::balance(0, &ALICE), INITIAL_BALANCE - withdraw_amount); + }); + + Relay::execute_with(|| { + // Parachain(1)'s sovereign account balance decreases + assert_eq!( + relay_chain::Balances::free_balance(parachain_sovereign_account_id(1)), + INITIAL_BALANCE - (withdraw_amount - fee_in_source) + ); + + // ALICE's balance in the relay chain increases + assert_eq!( + relay_chain::Balances::free_balance(&ALICE), + INITIAL_BALANCE + (withdraw_amount - fee_in_source - fee_in_destination) + ); }); } } diff --git a/examples/src/transfers/teleport.rs b/examples/src/transfers/teleport.rs index 0cc18d0..20b4aa5 100644 --- a/examples/src/transfers/teleport.rs +++ b/examples/src/transfers/teleport.rs @@ -11,16 +11,31 @@ mod tests { fn teleport_fungible() { MockNet::reset(); - let teleport_amount = 50 * CENTS; + let withdraw_amount = 50 * CENTS; + + let fee_in_source = relay_chain::estimate_message_fee(3); + let fee_in_destination = parachain::estimate_message_fee(4); + let message: Xcm = Xcm(vec![ - WithdrawAsset((Here, teleport_amount).into()), + WithdrawAsset((Here, withdraw_amount).into()), + BuyExecution { + fees: (Here, fee_in_source).into(), + weight_limit: WeightLimit::Unlimited, + }, InitiateTeleport { - assets: AllCounted(1).into(), + assets: All.into(), dest: Parachain(1).into(), - xcm: Xcm(vec![DepositAsset { - assets: AllCounted(1).into(), - beneficiary: Junction::AccountId32 { network: None, id: ALICE.into() }.into(), - }]), + xcm: Xcm(vec![ + BuyExecution { + fees: (Parent, fee_in_destination).into(), + weight_limit: WeightLimit::Unlimited, + }, + DepositAsset { + assets: All.into(), + beneficiary: Junction::AccountId32 { network: None, id: ALICE.into() } + .into(), + }, + ]), }, ]); @@ -33,93 +48,32 @@ mod tests { assert_eq!( relay_chain::Balances::free_balance(ALICE), - INITIAL_BALANCE - teleport_amount + INITIAL_BALANCE - withdraw_amount ); }); ParaA::execute_with(|| { let expected_message_received: Xcm = Xcm(vec![ - ReceiveTeleportedAsset(vec![(Parent, teleport_amount).into()].into()), + ReceiveTeleportedAsset( + vec![(Parent, withdraw_amount - fee_in_source).into()].into(), + ), ClearOrigin, + BuyExecution { + fees: (Parent, fee_in_destination).into(), + weight_limit: WeightLimit::Unlimited, + }, DepositAsset { - assets: AllCounted(1).into(), + assets: All.into(), beneficiary: Junction::AccountId32 { network: None, id: ALICE.into() }.into(), }, ]); assert_eq!(parachain::MsgQueue::received_dmp(), vec![expected_message_received]); - assert_eq!(parachain::Assets::balance(1, &ALICE), INITIAL_BALANCE + teleport_amount); - }); - } - - /// Scenario: - /// ALICE teleports her nft from the relay chain to parachain A - #[test] - fn teleport_nft() { - MockNet::reset(); - - Relay::execute_with(|| { - // Mint NFT for Alice on Relay chain - assert_ok!(relay_chain::Uniques::force_create( - relay_chain::RuntimeOrigin::root(), - 1, - ALICE, - true - )); - assert_ok!(relay_chain::Uniques::mint( - relay_chain::RuntimeOrigin::signed(ALICE), - 1, - 42, - ALICE - )); - - assert_eq!(relay_chain::Uniques::owner(1, 42), Some(ALICE)); - }); - - ParaA::execute_with(|| { - // Create NFT collection representing the relay chain one - assert_ok!(parachain::ForeignUniques::force_create( - parachain::RuntimeOrigin::root(), - 1u32, - ALICE, - false - )); - - // Alice is Collection Owner. - assert_eq!(parachain::ForeignUniques::collection_owner(1u32), Some(ALICE)); - // Alice does not own Collection Item 42 yet. - assert_eq!(parachain::ForeignUniques::owner(1u32, 42u32.into()), None); - assert_eq!(parachain::Balances::reserved_balance(&ALICE), 0); - }); - - let message: Xcm = Xcm(vec![ - WithdrawAsset((GeneralIndex(1), 42u64).into()), - InitiateTeleport { - assets: AllCounted(1).into(), - dest: Parachain(1).into(), - xcm: Xcm(vec![DepositAsset { - assets: AllCounted(1).into(), - beneficiary: Junction::AccountId32 { id: ALICE.into(), network: None }.into(), - }]), - }, - ]); - - Relay::execute_with(|| { - assert_ok!(relay_chain::XcmPallet::execute( - relay_chain::RuntimeOrigin::signed(ALICE), - Box::new(xcm::VersionedXcm::V3(message.into())), - (100_000_000_000, 100_000_000_000).into(), - )); - }); - - ParaA::execute_with(|| { - assert_eq!(parachain::ForeignUniques::owner(1u32, 42u32.into()), Some(ALICE)); - assert_eq!(parachain::Balances::reserved_balance(&ALICE), 1000); - }); - - Relay::execute_with(|| { - assert_eq!(relay_chain::Uniques::owner(1, 42), None); + assert_eq!( + parachain::Assets::balance(0, &ALICE), + INITIAL_BALANCE + (withdraw_amount - fee_in_source - fee_in_destination) + ); }); } } diff --git a/examples/src/trap_and_claim/mod.rs b/examples/src/trap_and_claim/mod.rs index 1651b00..e785f5f 100644 --- a/examples/src/trap_and_claim/mod.rs +++ b/examples/src/trap_and_claim/mod.rs @@ -14,9 +14,11 @@ mod tests { /// It then deposits the assets in the account of ALICE. #[test] fn trap_and_claim_assets() { + MockNet::reset(); + let message = Xcm(vec![ + UnpaidExecution { weight_limit: WeightLimit::Unlimited, check_origin: None }, WithdrawAsset((Here, 10 * CENTS).into()), - BuyExecution { fees: (Here, CENTS).into(), weight_limit: WeightLimit::Unlimited }, Trap(0), // <-- Errors DepositAsset { // <-- Not executed because of error. @@ -33,6 +35,7 @@ mod tests { }); let claim_message = Xcm(vec![ + UnpaidExecution { weight_limit: WeightLimit::Unlimited, check_origin: None }, ClaimAsset { assets: (Here, 10 * CENTS).into(), ticket: Here.into() }, ReportHolding { response_info: QueryResponseInfo { diff --git a/examples/src/version_subscription/mod.rs b/examples/src/version_subscription/mod.rs index c48e5ff..1d67fd6 100644 --- a/examples/src/version_subscription/mod.rs +++ b/examples/src/version_subscription/mod.rs @@ -14,11 +14,14 @@ mod tests { fn subscribe_and_unsubscribe_version() { MockNet::reset(); + let message_fee = relay_chain::estimate_message_fee(3); + let query_id_set = 1234; - let message = Xcm(vec![SubscribeVersion { - query_id: query_id_set, - max_response_weight: Weight::from_all(0), - }]); + let message = Xcm(vec![ + WithdrawAsset((Here, message_fee).into()), + BuyExecution { fees: (Here, message_fee).into(), weight_limit: WeightLimit::Unlimited }, + SubscribeVersion { query_id: query_id_set, max_response_weight: Weight::from_all(0) }, + ]); ParaA::execute_with(|| { assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); From 8cb382008dd6db221c14e9be01f0ffaf8d515e2c Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Thu, 1 Jun 2023 12:52:44 +0200 Subject: [PATCH 52/73] update draft, add testing chapter and fix links --- examples/README.md | 26 +++++++++++++++++++++- examples/src/holding_modifiers/mod.rs | 2 +- src/SUMMARY.md | 16 +++---------- src/executor_config/README.md | 4 ++-- src/fundamentals/multilocation/junction.md | 2 +- src/fundamentals/weight_and_fees.md | 6 ++--- src/journey/expects.md | 21 +++++------------ src/journey/fees/README.md | 6 ++--- src/journey/holding-modifiers.md | 4 ++-- src/journey/locks/locks.md | 4 ++-- src/journey/queries.md | 8 +++---- src/journey/transact.md | 24 ++++++++++++++------ src/journey/transfers/README.md | 6 ++--- src/journey/transfers/reserve.md | 4 ++-- src/journey/transfers/teleports.md | 4 ++-- src/journey/trap-and-claim.md | 2 +- src/journey/version.md | 4 ++-- src/overview/format.md | 2 +- src/quickstart/README.md | 2 +- src/quickstart/first-look.md | 2 +- src/testing/README.md | 17 ++++++++++++-- src/transport_protocols/README.md | 8 ------- 22 files changed, 96 insertions(+), 78 deletions(-) delete mode 100644 src/transport_protocols/README.md diff --git a/examples/README.md b/examples/README.md index b65cada..887530d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,2 +1,26 @@ # xcm-examples -This repository shows the xcm examples for the xcm docs +This repository contains the xcm examples for the xcm docs. +The examples are set up using the [XCM-simulator](https://github.com/paritytech/polkadot/tree/master/xcm/xcm-simulator). +The testnet can be found in `examples/src/simple_test_net`. + +#### How to run +To run the examples, do the following: +1. Clone the repository: +`git clone https://github.com/paritytech/xcm-docs.git` + +2. cd to the examples folder: +`cd examples/` + +3. Run all the tests: +`cargo test` +or a single test: +`cargo test -p xcm-examples trap_and_claim_assets -- --nocapture` + +#### events printing +You can print out the events on a parachain or the relay chain using the `print_para_events` or `print_relay_events` functions. The functions are used in a parachain or relay chain `TestExternalities`: + +```rust +ParaA::execute_with(|| { + print_para_events(); +}); +``` diff --git a/examples/src/holding_modifiers/mod.rs b/examples/src/holding_modifiers/mod.rs index 1b05b76..8b3f05e 100644 --- a/examples/src/holding_modifiers/mod.rs +++ b/examples/src/holding_modifiers/mod.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use crate::simple_test_net::{parachain::RelayNativeAsset, *}; + use crate::simple_test_net::*; use frame_support::{assert_ok, pallet_prelude::Weight}; use xcm::latest::prelude::*; use xcm_simulator::TestExt; diff --git a/src/SUMMARY.md b/src/SUMMARY.md index c3ac26f..ffdf6c6 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -21,8 +21,8 @@ - [Transfers](journey/transfers/README.md) - [Asset teleportation](journey/transfers/teleports.md) - [Reserve-backed transfers](journey/transfers/reserve.md) - - [Transact: A general solution](journey/transact.md) - [Fee handling](journey/fees/README.md) + - [Transact: A general solution](journey/transact.md) - [Origin manipulation](journey/origins.md) - [More register modifiers](journey/register-modifiers.md) - [More Holding Modifiers](./journey/holding-modifiers.md) @@ -32,22 +32,12 @@ - [XCM Version](journey/version.md) - [Locks](journey/locks/locks.md) - [Channels and Bridges](journey/channels-and-bridges.md) - - [Misc]() - [Config Deep Dive](executor_config/README.md) - [Testing](testing/README.md) - - [Separation of concerns]() - - [Simulating message execution]() - - [How to test my own configuration]() - - [Testing the full XCM Journey]() -- [Transport Protocols](transport_protocols/README.md) - - [VMP]() - - [HRMP]() - - [XCMP]() # Reference -- [Cookbook]() -- [All Instructions]() + - [All XCVM Registers](reference/xcvm-registers.md) -[Next Steps]() diff --git a/src/executor_config/README.md b/src/executor_config/README.md index 5d48679..fb10629 100644 --- a/src/executor_config/README.md +++ b/src/executor_config/README.md @@ -79,7 +79,7 @@ The `RuntimeCall` type is equal to the RuntimeCall created in the `construct_run ### XcmSender The `XcmSender` type implements the `SendXcm` trait, and defines how the xcm_executor can send XCMs (which transport layer it can use for the XCMs). -This type normally implements a tuple for one or more [transport layer(s)](Todo Transport Layer Link). +This type normally implements a tuple for one or more [transport layer(s)](https://wiki.polkadot.network/docs/learn-xcm-transport). For example a parachain can implement the XcmSender as: ```rust,noplayground ( @@ -170,7 +170,7 @@ The most used is the `FixedWeightBounds`: // BaseXcmWeight is a const weight. FixedWeightBounds; ``` -Note: [More information](Todo) about weight. +Note: [More information](../fundamentals/weight_and_fees.md) about weight. ### Trader The `Trader` type is responsible for buying weight in the `BuyExecution` instruction using assets in the holding register and to refund unspend weight. diff --git a/src/fundamentals/multilocation/junction.md b/src/fundamentals/multilocation/junction.md index e1550c1..0ae4b9e 100644 --- a/src/fundamentals/multilocation/junction.md +++ b/src/fundamentals/multilocation/junction.md @@ -1,5 +1,5 @@ # Junction(s) -In the section on [MultiLocations](README.md), we looked at the MultiLocation struct. +In the section on [MultiLocations](index.html), we looked at the MultiLocation struct. We talked about the Multilocation being a way to describe moving from one place in the system hierarchy to another. The `parents` parameter expresses the number of steps up in the hierarchy. In this section, we dive further into the MultiLocation struct and explain how we can use the Junctions type to describe steps in the system hierarchy. diff --git a/src/fundamentals/weight_and_fees.md b/src/fundamentals/weight_and_fees.md index cf519a3..c665419 100644 --- a/src/fundamentals/weight_and_fees.md +++ b/src/fundamentals/weight_and_fees.md @@ -15,16 +15,16 @@ Weight, however, is static, defined beforehand, which makes XCM execution lighte The principle behind weight payment is to pay for what you use, so the two stages of XCM where fees are paid are *sending* the message and actually *executing* it. The fees for sending are paid on the local system, usually by the origin of the message, because we are using the message delivery mechanism maintained by the origin. Similarly, the execution fees are paid on the destination system, via the `BuyExecution` instruction. In other words, XCMs are paid for via their own instructions. -We'll talk more about `BuyExecution` in the [fee handling chapter](TODO:add_link). +We'll talk more about `BuyExecution` in the [fee handling chapter](../journey/fees/index.html). XCM is agnostic, which means it doesn't assume fees need to be paid. It's entirely possible to not pay for the effects of an XCM on the destination system. Even in systems where fees have to be paid, special cases of free execution can be made. -There are security measures systems can put in place (see [barrier](TODO:add_link)) to not execute XCMs that do not pay for their fees. +There are security measures systems can put in place (see [barrier](../executor_config/index.html#barrier)) to not execute XCMs that do not pay for their fees. ## Executor config -The executor has a `Weigher` [configuration item](TODO:add_link) that specifies the weight of each instruction. +The executor has a `Weigher` [configuration item](../executor_config/index.html#weigher) that specifies the weight of each instruction. It weighs the whole message by adding the weight of each instruction. A simple way of weighing instructions is to assign them a base weight value to all of them. This works, but it is not very accurate, as different instructions use more resources when being executed. diff --git a/src/journey/expects.md b/src/journey/expects.md index 3cf45f8..9747cc5 100644 --- a/src/journey/expects.md +++ b/src/journey/expects.md @@ -18,7 +18,7 @@ ExpectAsset(MultiAssets) ### Example -For the full example, check [here](https://github.com/paritytech/xcm-docs). +For the full example, check [here](https://github.com/paritytech/xcm-docs/tree/main/examples). ```rust, noplayground WithdrawAsset((Here, AMOUNT).into()), @@ -45,7 +45,7 @@ ExpectOrigin(Option) ### Example -For the full example, check [here](https://github.com/paritytech/xcm-docs). +For the full example, check [here](https://github.com/paritytech/xcm-docs/tree/main/examples). The `ExpectOrigin` instruction errors because the `ClearOrigin` clears the origin register and we expect it to be equal to `Parachain(1)`. ```rust,noplayground // Set the instructions that are executed when ExpectOrigin does not pass. @@ -81,7 +81,7 @@ ExpectPallet { ``` ### Example -For the full example, check [here](https://github.com/paritytech/xcm-docs). +For the full example, check [here](https://github.com/paritytech/xcm-docs/tree/main/examples). ```rust, noplayground // Set the instructions that are executed when ExpectPallet does not pass. // In this case, reporting back an error to the Parachain. @@ -112,7 +112,7 @@ The `ExpectError` instruction allows to only execute the instructions in the err ### Example -For the full example, check [here](https://github.com/paritytech/xcm-docs). +For the full example, check [here](https://github.com/paritytech/xcm-docs/tree/main/examples). ```rust,noplayground SetErrorHandler(Xcm(vec![ @@ -135,21 +135,10 @@ ExpectPallet { ## ExpectTransactStatus The `ExpectTransactStatus` instruction throws an `ExpectationFalse` error if the transact status register does not equal the expected transact status. -The status is described by the `MaybeErrorCode` enum, and can either be a Success, Error or TruncatedError if the length of the error exceeds the MaxDispatchErrorLen. -For pallet-based calls, the Error is represented as the scale encoded `Error` enum of the called pallet. -```rust,noplayground -ExpectTransactStatus(MaybeErrorCode) - -pub enum MaybeErrorCode { - Success, - Error(BoundedVec), - TruncatedError(BoundedVec), -} -``` ### Example -For the full example, check [here](https://github.com/paritytech/xcm-docs). +For the full example, check [here](https://github.com/paritytech/xcm-docs/tree/main/examples). The transact status is reported to `Parachain(1)` if the call in the `Transact` errors. ```rust,noplayground diff --git a/src/journey/fees/README.md b/src/journey/fees/README.md index 12e2f4b..d15b06f 100644 --- a/src/journey/fees/README.md +++ b/src/journey/fees/README.md @@ -11,7 +11,7 @@ BuyExecution { fees: MultiAsset, weight_limit: WeightLimit } This instruction is used to buy weight using fees. While in some cases there's no need to pay for execution (if you control both systems for example), in most cases you'll need to add this instruction. -There's a predefined [barrier](../../executor_config/index.md), `AllowTopLevelPaidExecutionFrom`, that explicitly drops messages that do not include this instruction. +There's a predefined [barrier](../../executor_config/index.md#barrier), `AllowTopLevelPaidExecutionFrom`, that explicitly drops messages that do not include this instruction. Let's grab the teleport message from the [transfers chapter](../transfers/teleports.md) and add fee payment. @@ -67,7 +67,7 @@ UnpaidExecution { weight_limit: WeightLimit, check_origin: Option This instruction is used for explicitly stating this message shouldn't be paid for. It can be used as a way of identifying certain priviledged messages that don't pay fees, coming from a particular system. -This instruction can be searched for in [barriers](../../executor_config/index.md) to allow this. +This instruction can be searched for in [barriers](../../executor_config/index.md#barrier) to allow this. Make sure you trust the origin system because it won't be paying fees. There's already a predefined barrier in xcm-builder, `AllowExplicitUnpaidExecutionFrom`, that makes sure this is the first instruction in the message. As always, you can build your own for your own use-cases. @@ -109,4 +109,4 @@ let message = Xcm(vec![ In this example, we pay upfront for all the transactions we do later with the `DepositAsset` instructions. If any transaction throws an error (for example, due to lack of funds), the error handler will be called and the weight for all the instructions that weren't executed is refunded. -For the full example, check our [repo](https://github.com/paritytech/xcm-docs). +For the full example, check our [repo](https://github.com/paritytech/xcm-docs/tree/main/examples). diff --git a/src/journey/holding-modifiers.md b/src/journey/holding-modifiers.md index 940ba1f..77185ff 100644 --- a/src/journey/holding-modifiers.md +++ b/src/journey/holding-modifiers.md @@ -11,7 +11,7 @@ BurnAsset(MultiAssets) The `BurnAsset` instruction allows for the reduction of assets in the Holding Register by up to the specified assets. The execution of the instruction does not throw an error if the Holding Register does not contain the assets (to make this an error, use `ExpectAsset` prior). ### Example -For the full example, check [the repo](https://github.com/paritytech/xcm-docs). +For the full example, check [the repo](https://github.com/paritytech/xcm-docs/tree/main/examples). The Scenario of the example is as follows: Parachain A withdraws 10 units from its sovereign account on the relay chain and burns 4 of them. The relay chain then reports back the status of the Holding Register to Parachain A. We expect the Holding Register to hold 6 units. @@ -53,7 +53,7 @@ and receive accordingly more assets then stated in `want`. If the `maximal` fiel order to receive as little as possible while receiving at least `want`. ### Example -The full example can be found in [the repo](https://github.com/paritytech/xcm-docs). +The full example can be found in [the repo](https://github.com/paritytech/xcm-docs/tree/main/examples). The scenario for the example is this: Scenario: diff --git a/src/journey/locks/locks.md b/src/journey/locks/locks.md index 9cdac59..b3459f5 100644 --- a/src/journey/locks/locks.md +++ b/src/journey/locks/locks.md @@ -63,7 +63,7 @@ This principle becomes more clear in the second example. ### Example 1 -Check out the full [example code](https://github.com/paritytech/xcm-docs). +Check out the full [example code](https://github.com/paritytech/xcm-docs/tree/main/examples). The scenario of this example is as follows: Parachain A locks 5 Cents of relay chain native assets of its Sovereign account on the relay chain and assigns Parachain B as unlocker. @@ -113,7 +113,7 @@ assert_eq!( ### Example 2 -Check out the full [example code](https://github.com/paritytech/xcm-docs). +Check out the full [example code](https://github.com/paritytech/xcm-docs/tree/main/examples). The scenario of this example is as follows: Parachain A sets two locks on the relay chain with as unlockers Parachain B and Parachain C. diff --git a/src/journey/queries.md b/src/journey/queries.md index a32db8a..ea27700 100644 --- a/src/journey/queries.md +++ b/src/journey/queries.md @@ -66,7 +66,7 @@ The `ReportHolding` instruction reports to the given destination the contents of ### Example -For the full example, check [here](https://github.com/paritytech/xcm-docs). Assets are withdrawn from the account of parachain 1 on the relay chain and partly deposited in the account of parachain 2. The remaining assets are reported back to parachain 1. +For the full example, check [here](https://github.com/paritytech/xcm-docs/tree/main/examples). Assets are withdrawn from the account of parachain 1 on the relay chain and partly deposited in the account of parachain 2. The remaining assets are reported back to parachain 1. ```rust, noplayground Xcm(vec![ @@ -109,7 +109,7 @@ pub struct PalletInfo { ``` ### Example -For the full example, check [here](https://github.com/paritytech/xcm-docs). It queries for all instances of pallet_balances and sends the result back to parachain 1. +For the full example, check [here](https://github.com/paritytech/xcm-docs/tree/main/examples). It queries for all instances of pallet_balances and sends the result back to parachain 1. ```rust, noplayground Xcm(vec![ @@ -133,7 +133,7 @@ ReportError(QueryResponseInfo) ``` ### Example -For the full example, check [here](https://github.com/paritytech/xcm-docs). The message sets the error handler to report back any error that is thrown during execution of the instructions using the `ReportError` instruction. +For the full example, check [here](https://github.com/paritytech/xcm-docs/tree/main/examples). The message sets the error handler to report back any error that is thrown during execution of the instructions using the `ReportError` instruction. ```rust, noplayground Xcm(vec![ // Set the Error Handler to report back status of Error register. @@ -157,7 +157,7 @@ ReportTransactStatus(QueryResponseInfo) ### Example -For the full example, check [here](https://github.com/paritytech/xcm-docs). +For the full example, check [here](https://github.com/paritytech/xcm-docs/tree/main/examples). Dispatches a call on the consensus system receiving this Xcm and reports back the status of the Transact Status Register. ```rust,noplayground diff --git a/src/journey/transact.md b/src/journey/transact.md index 366c8d4..10dd244 100644 --- a/src/journey/transact.md +++ b/src/journey/transact.md @@ -34,12 +34,22 @@ Hence, the field is a byte vector that can be freely interpreted in whatever for However, the XCVM does not inherently know how to interpret this call field nor how to decode it; it is reliant on the `T` type parameter to specify the proper codec for the byte vector. Instead of just using a `Vec` we use `DoubleEncoded` as a wrapper around a pre-encoded call (`Vec`) with extra functionalities such as caching of the decoded value. We like to emphasize that the call in the `Transact` instruction can be anything from a `RuntimeCall` in a FRAME-based system, to a smart contract function call in an EVM-based system. - -[//]: # (Todo: Move Transact Status explanation from expect to here.) Each XCVM has a Transact Status Register, to record the execution result of the call that is dispatched by the `Transact` instruction. *Important note:* The execution of the XCM instruction does *not* error when the dispatched call errors. +The status is described by the `MaybeErrorCode` enum, and can either be a Success, Error or TruncatedError if the length of the error exceeds the MaxDispatchErrorLen. +For pallet-based calls, the Error is represented as the scale encoded `Error` enum of the called pallet. +```rust,noplayground +ExpectTransactStatus(MaybeErrorCode) + +pub enum MaybeErrorCode { + Success, + Error(BoundedVec), + TruncatedError(BoundedVec), +} +``` + ## XCM Executor In this section, we quickly look at how the XCM executor executes the `Transact` instruction. @@ -53,7 +63,7 @@ It executes, among other things, the following steps: ## Example 1 -For the full example, check [the repo](https://github.com/paritytech/xcm-docs). +For the full example, check [the repo](https://github.com/paritytech/xcm-docs/tree/main/examples). In this example, the relay chain executes the `set_balance` function of `pallet_balances` on `Parachain(1)`. This function requires the origin to be root. We enable the root origin for the relay chain by setting `ParentAsSuperuser` for the `OriginConverter` config type. @@ -78,7 +88,7 @@ let message = Xcm(vec![ ``` ## Example 2 -For the full example, check [the repo](https://github.com/paritytech/xcm-docs). +For the full example, check [the repo](https://github.com/paritytech/xcm-docs/tree/main/examples). In this example, as Parachain(1), we create an NFT collection on the relay chain and we then mint an NFT with ID 1. The admin for the nft collection is parachain(1). The call looks as follows: @@ -126,6 +136,6 @@ let message = Xcm(vec![ ## Next: Check out the following instructions that interact with the Transact Status Register: -- [ClearTransactStatus](TODO) -- [ReportTransactStatus](TODO) -- [ExpectTransactStatus](TODO) \ No newline at end of file +- [ClearTransactStatus](register-modifiers.md#cleartransactstatus) +- [ReportTransactStatus](queries.md#reporttransactstatus) +- [ExpectTransactStatus](expects.md#expecttransactstatus) \ No newline at end of file diff --git a/src/journey/transfers/README.md b/src/journey/transfers/README.md index 536f5db..d3c9f24 100644 --- a/src/journey/transfers/README.md +++ b/src/journey/transfers/README.md @@ -22,7 +22,7 @@ BuyExecution { fees: MultiAssets, weight_limit: WeightLimit }, Because XCM is designed to be agnostic to the underlying consensus system, it doesn't have fee payment baked in. This instruction lets you pay for the execution of the XCM using the assets in the holding register. -Most XCMs are not allowed to be executed (blocked by the [barrier](../../executor_config/index.md)) if they don't contain this instruction as one of the first ones to pay for all future ones. +Most XCMs are not allowed to be executed (blocked by the [barrier](../../executor_config/index.md#barrier)) if they don't contain this instruction as one of the first ones to pay for all future ones. ## DepositAsset @@ -30,7 +30,7 @@ Most XCMs are not allowed to be executed (blocked by the [barrier](../../executo DepositAsset { assets: MultiAssetFilter, beneficiary: MultiLocation }, ``` -This instruction will put assets from the holding register that match the [MultiAssetFilter](../../fundamentals/multiasset.md) into the `beneficiary`. +This instruction will put assets from the holding register that match the [MultiAssetFilter](../../fundamentals/multiasset.md#multiassetfilter) into the `beneficiary`. Note that `beneficiary` must be a location where the local consensus system can actually deposit assets to, e.g. it doesn't make sense to deposit assets to `../AccountId32(0x0)`. ## Example @@ -47,7 +47,7 @@ let message = Xcm(vec![ ``` As we've seen, the above message results in withdrawing assets from the origin of the message, paying for execution and depositing the rest to another account on the same system. -The full example can be seen in [the repo](https://github.com/paritytech/xcm-docs). +The full example can be seen in [the repo](https://github.com/paritytech/xcm-docs/tree/main/examples). ## Transferring between systems diff --git a/src/journey/transfers/reserve.md b/src/journey/transfers/reserve.md index d6b80c2..1f76a92 100644 --- a/src/journey/transfers/reserve.md +++ b/src/journey/transfers/reserve.md @@ -90,7 +90,7 @@ This new XCM contains the following instructions, in order: 2. ClearOrigin 3. All instructions specified in the `xcm` operand, in this case `DepositReserveAsset` -As was the case with [teleports](../teleports.md), instructions 1. and 2. are added automatically by the executor when using `InitiateReserveWithdraw`. +As was the case with [teleports](teleports.md), instructions 1. and 2. are added automatically by the executor when using `InitiateReserveWithdraw`. Upon receiving this XCM, the reserve will withdraw the asset from parachain 1's sovereign account (where the real asset is stored), and deposit it on parachain 2's sovereign account. @@ -170,4 +170,4 @@ It's the sender the one who doesn't need to trust the destination, since it'll e ## Next steps -Next, we'll talk about a very important topic we mentioned before but skipped in this chapter, paying fees for the effects our XCMs have. +Next, we'll talk about a very important topic we mentioned before but skipped in this chapter, [paying fees](../fees/index.html) for the effects our XCMs have. diff --git a/src/journey/transfers/teleports.md b/src/journey/transfers/teleports.md index 3722a1d..6121100 100644 --- a/src/journey/transfers/teleports.md +++ b/src/journey/transfers/teleports.md @@ -81,7 +81,7 @@ This instruction is a *trusted indication*. It should only be executed if the or This level of care must be taken because this instruction will *put assets into the circulating supply*, usually minting them. As specified earlier, this can result in an increase/decrease in circulating supply of an asset, or a duplication/loss of an NFT, if the source is not trusted for this purpose. -You can set which origins are allowed (act as teleporters) by configuring the [IsTeleporter](../../executor_config/index.md) type in the XCM executor. +You can set which origins are allowed (act as teleporters) by configuring the [IsTeleporter](../../executor_config/index.md##isteleporter) type in the XCM executor. If the origin is not allowed to teleport assets to this system, an `UntrustedTeleportLocation` error is returned. This instruction will populate the holding register with the teleported assets, which can be used by further instructions. @@ -125,4 +125,4 @@ The example assumes an NFT with index 42 inside a collection with index 1. ## Next steps -We'll look at reserve-backed transfers next. +We'll look at reserve-backed transfers [next](reserve.md). diff --git a/src/journey/trap-and-claim.md b/src/journey/trap-and-claim.md index 4d018ba..be953b8 100644 --- a/src/journey/trap-and-claim.md +++ b/src/journey/trap-and-claim.md @@ -23,7 +23,7 @@ This must match exactly with the assets claimable by the origin. The `ticket` field is an identifier that helps locating the asset. It is, for example, useful for distinguishing between Asset Versions. Lets say we have an XCM V2 trapped asset and send an XCM V3 `ClaimAsset` instruction, then the `ticket` field can be used to tell between the versions. In the xcm-pallet, `Here` is used to describe the same version as the `ClaimAsset` instruction, while the `GeneralIndex` Junction is used to describe other XCM versions. ## Example -The full example can be found [here](TODO). +The full example can be found [here](https://github.com/paritytech/xcm-docs/tree/main/examples). The scenario of the example is this: Parachain A withdraws funds from its sovereign account on the relay chain. diff --git a/src/journey/version.md b/src/journey/version.md index de3c31c..a893852 100644 --- a/src/journey/version.md +++ b/src/journey/version.md @@ -4,7 +4,7 @@ XCM is a versioned messaging format. One version may contain more or different i - `UnsubscribeVersion` The version subscription model can differ per XCVM implementation. -The `xcm-executor` has a `SubscriptionService` [config item](../executor_config/index.md). +The `xcm-executor` has a `SubscriptionService` [config item](../executor_config/index.md#subscriptionservice). Any type specified as the `SubscriptionService` must implement the `VersionChangeNotifier` trait. The XCM pallet is one such implementor. When the `SubscribeVersion` instruction is sent to a consensus system that uses the XCM pallet as the `SubscriptionService` in the XCM executor, the system will send back its currently `AdvertisedVersion` and will keep the subscribed location up to date when the version changes. @@ -20,4 +20,4 @@ SubscribeVersion { UnsubscribeVersion ``` -Check out the [example](https://github.com/paritytech/xcm-docs). +Check out the [example](https://github.com/paritytech/xcm-docs/tree/main/examples). diff --git a/src/overview/format.md b/src/overview/format.md index da3ce0e..aca311b 100644 --- a/src/overview/format.md +++ b/src/overview/format.md @@ -3,7 +3,7 @@ It's essential to understand that XCM is a format, not a protocol. It describes how messages should be structured and contains instructions relevant to the on-chain actions the message intends to perform. However, XCM does not dictate how messages are delivered. -That responsibility falls on [transport layer protocols](../transport_protocols/index.md) such as XCMP (Cross Chain Message Passing) and VMP (Vertical Message Passing) in the Polkadot ecosystem, or any others to come. +That responsibility falls on [transport layer protocols](https://wiki.polkadot.network/docs/learn-xcm-transport) such as XCMP (Cross Chain Message Passing) and VMP (Vertical Message Passing) in the Polkadot ecosystem, or any others to come. This separation of concerns is useful, since it allows us to think of the interactions we want to build between systems without having to think about how the messages involved are actually routed. diff --git a/src/quickstart/README.md b/src/quickstart/README.md index 67839c3..11952e8 100644 --- a/src/quickstart/README.md +++ b/src/quickstart/README.md @@ -7,7 +7,7 @@ A pre-requisite for using XCM is to have a stable Rust version and Cargo install ## Running the Examples -All examples in the documentation are located in the [repository](https://github.com/paritytech/xcm-docs). Follow these steps to run the `first-look` example. +All examples in the documentation are located in the [repository](https://github.com/paritytech/xcm-docs/tree/main/examples). Follow these steps to run the `first-look` example. First clone the repository: ```shell diff --git a/src/quickstart/first-look.md b/src/quickstart/first-look.md index cecae90..8ceecad 100644 --- a/src/quickstart/first-look.md +++ b/src/quickstart/first-look.md @@ -1,5 +1,5 @@ # First Look -In this section, we take you through a simple example of an XCM. In this example, we withdraw the native token from the account of Alice and deposit this token in the account of Bob. This message simulates a transfer between two accounts in the same consensus system (`ParaA`). You can find the complete code example [in the repo](https://github.com/paritytech/xcm-docs). +In this section, we take you through a simple example of an XCM. In this example, we withdraw the native token from the account of Alice and deposit this token in the account of Bob. This message simulates a transfer between two accounts in the same consensus system (`ParaA`). You can find the complete code example [in the repo](https://github.com/paritytech/xcm-docs/tree/main/examples). ## Message ```rust,noplayground diff --git a/src/testing/README.md b/src/testing/README.md index 1c05856..8b7d966 100644 --- a/src/testing/README.md +++ b/src/testing/README.md @@ -7,7 +7,20 @@ There are different levels for testing, which should be tackled sequentially: - End-to-end: Making sure the whole flow works, in an environment as similar to production as possible. We'll discuss tools and best practices for each of these levels. +## XCM Simulator +The [xcm-simulator](https://github.com/paritytech/polkadot/tree/master/xcm/xcm-simulator) is a tool to quickly test the execution of various XCM instructions against the `xcm-executor`. +The examples in this documentation use the xcm-simulator. +The simulator mocks the Downward Message Passing pallet, enabling us to get the XCMs that a parachain receives from the relay chain using the `received_dmp` getter. +The simulator should be used as a XCM playground. For testing the configuration of your parachain and the integration with other chains, you can use the xcm-emulator. -## Coming soon +## XCM Emulator +The [xcm-emulator](https://github.com/paritytech/cumulus/tree/master/xcm/xcm-emulator) is a tool to emulator XCM program execution using pre-configured runtimes, including those used to run on live networks, such as Kusama, Polkadot, Statemine et cetera. This allows for testing cross-chain message passing and verifying outcomes, weights, and side-effects. -This chapter is still being worked on. +An example of how the emulator is used for testing common good parachains can be found [here](https://github.com/paritytech/cumulus/tree/master/parachains/integration-tests/emulated). + +The xcm-emulator uses the transport layer pallets. However, the messages do not physically go through the same messaging infrastructure as on live networks, so some code is not being tested. Also, consensus related events can not be tested, like disputes, staking and iamonline events. To test for these events, parachains can use E2E tests. + +## End-to-End testing +There are two frameworks being used in the DotSama ecosystem to do e2e testing: +- [Zombienet](https://github.com/paritytech/zombienet). +- [Chopsticks](https://github.com/AcalaNetwork/chopsticks). \ No newline at end of file diff --git a/src/transport_protocols/README.md b/src/transport_protocols/README.md deleted file mode 100644 index 97a5a22..0000000 --- a/src/transport_protocols/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Transport Protocols - -When building a tool or product using XCM, you usually don't need to concern yourself with the transport protocols used to actually route the messages between systems. -However, if you need to learn about them or are just curious about how they work, we'll detail the existing ones in the Dotsama ecosystem. - -## Coming soon - -This chapter is still being worked on. From 870c76c02845b0372e4cef63acf3732fcaee3d6c Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Thu, 1 Jun 2023 13:12:16 +0200 Subject: [PATCH 53/73] add list of tests --- examples/README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/examples/README.md b/examples/README.md index 887530d..29a8331 100644 --- a/examples/README.md +++ b/examples/README.md @@ -24,3 +24,29 @@ ParaA::execute_with(|| { print_para_events(); }); ``` + +#### Tests +- `first_look` +- `transfers/teleport_fungible` +- `transfers/reserve_backed_transfer_para_to_para` +- `transfers/reserve_backed_transfer_relay_to_para` +- `transfers/reserve_backed_transfer_para_to_relay` +- `transact/transact_set_balance` +- `transact/transact_mint_nft` +- `origins/descend_origin` +- `holding_modifiers/burn_assets` +- `holding_modifiers/exchange_asset_maximal_true` +- `holding_modifiers/exchange_asset_maximal_false` +- `trap_and_claim/trap_and_claim_assets` +- `expects/expect_asset` +- `expects/expect_origin` +- `expects/expect_pallet` +- `expects/expect_error` +- `expects/expect_transact_status` +- `queries/query_holding` +- `queries/query_pallet` +- `queries/report_error` +- `queries/report_transact_status` +- `version_subscription/subscribe_and_unsubscribe_version` +- `locks/remote_locking_on_relay` +- `locks/locking_overlap` From 140e4b26786e49352feabd279505120636a46631 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Mon, 5 Jun 2023 08:38:29 +0200 Subject: [PATCH 54/73] add fee test plus some extra fixes --- examples/src/fees/mod.rs | 52 +++++++++++++++++++++++ examples/src/first_look.rs | 2 +- examples/src/forum.rs | 65 ----------------------------- examples/src/lib.rs | 1 + examples/src/queries/mod.rs | 1 - examples/src/simple_test_net/mod.rs | 4 +- src/journey/fees/README.md | 29 ++++++++----- 7 files changed, 74 insertions(+), 80 deletions(-) create mode 100644 examples/src/fees/mod.rs delete mode 100644 examples/src/forum.rs diff --git a/examples/src/fees/mod.rs b/examples/src/fees/mod.rs new file mode 100644 index 0000000..50fbbff --- /dev/null +++ b/examples/src/fees/mod.rs @@ -0,0 +1,52 @@ +#[cfg(test)] +mod tests { + use crate::simple_test_net::{*, parachain::estimate_message_fee}; + use frame_support::assert_ok; + use xcm::latest::prelude::*; + use xcm_simulator::TestExt; + + /// Scenario: + /// Relay chain sends a XCM to Parachain A. + /// Enough execution is bought for the full message. + /// However, the message errors at the Trap(1) instruction (simulates error in other instructions). + /// The last three instructions are not executed + /// and the weight surplus of these instructions is refunded to the relay chain account. + #[test] + fn refund_surplus() { + MockNet::reset(); + let message_fee = parachain::estimate_message_fee(9); + let message = Xcm(vec![ + WithdrawAsset((Parent, message_fee).into()), + BuyExecution { + fees: (Parent, message_fee).into(), + weight_limit: WeightLimit::Unlimited, + }, + SetErrorHandler(Xcm(vec![ + RefundSurplus, + DepositAsset { + assets: All.into(), + beneficiary: AccountId32 { + network: Some(ByGenesis([0; 32])), + id: relay_sovereign_account_id().into(), + } + .into(), + }, + ])), + Trap(1), + ClearOrigin, + ClearOrigin, + ClearOrigin, + ]); + + Relay::execute_with(|| { + assert_ok!(RelaychainPalletXcm::send_xcm(Here, Parachain(1), message.clone(),)); + }); + + ParaA::execute_with(|| { + assert_eq!( + ParachainAssets::balance(0, relay_sovereign_account_id()), + INITIAL_BALANCE - message_fee + estimate_message_fee(3) + ); + }) + } +} diff --git a/examples/src/first_look.rs b/examples/src/first_look.rs index 31362bd..bf9b696 100644 --- a/examples/src/first_look.rs +++ b/examples/src/first_look.rs @@ -44,7 +44,7 @@ mod tests { // Check if the funds are subtracted from the account of Alice and added to the account of Bob. assert_eq!(ParachainBalances::free_balance(ALICE), INITIAL_BALANCE - amount); assert_eq!(parachain::Assets::balance(0, ALICE), INITIAL_BALANCE - fee); - assert_eq!(ParachainBalances::free_balance(BOB), amount); + assert_eq!(ParachainBalances::free_balance(BOB), INITIAL_BALANCE + amount); }); } } diff --git a/examples/src/forum.rs b/examples/src/forum.rs deleted file mode 100644 index 28d47f1..0000000 --- a/examples/src/forum.rs +++ /dev/null @@ -1,65 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::simple_test_net::*; - use frame_support::assert_ok; - use xcm::v3::prelude::*; - use xcm_simulator::TestExt; - - const BOB: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([2u8; 32]); - const QUERY_ID: u64 = 1234; - - /// Scenario: - /// ALICE sends message from parachain A to parachain B - /// Goal is to move both A's native asset and B's native asset to BOB - fn forum() { - MockNet::reset(); - - let amount_a = 50 * CENTS; - let amount_b = 50 * CENTS; - - let message: Xcm<()> = Xcm(vec![ - SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { - destination: ParentThen(X1(Parachain(1))).into(), - query_id: QUERY_ID, - max_weight: 0.into(), - })])), - ReserveAssetDeposited((ParentThen(X1(Parachain(1))), amount_a).into()), - WithdrawAsset((Here, amount_b).into()), - ClearOrigin, - BuyExecution { fees: (Here, amount_b).into(), weight_limit: WeightLimit::Unlimited }, - DepositAsset { - assets: AllCounted(2).into(), - beneficiary: Junction::AccountId32 { network: None, id: BOB.clone().into() }.into(), - }, - ]); - - let destination: MultiLocation = (Parent, Parachain(2)).into(); - let alice = Junction::AccountId32 { id: ALICE.into(), network: None }; - - ParaA::execute_with(|| { - assert_ok!(parachain::PolkadotXcm::send_xcm(alice, destination, message)); - - // ALICE on ParaA does not give up any balance - assert_eq!(parachain::Balances::free_balance(ALICE), INITIAL_BALANCE); - }); - - ParaA::execute_with(|| { - dbg!(¶chain::MsgQueue::received_dmp()); - }); - - ParaB::execute_with(|| { - dbg!(parachain::Balances::free_balance(sibling_account_sovereign_account_id(1, ALICE))); - - // ALICE does give up balance of ParaB's native asset... - assert_eq!( - parachain::Balances::free_balance(sibling_account_sovereign_account_id(1, ALICE)), - INITIAL_BALANCE - amount_b - ); - // ...and gives it to BOB - assert_eq!(parachain::Balances::free_balance(BOB), amount_b); - - // ParaA's native asset is minted and deposited to BOB's account - assert_eq!(parachain::Assets::balance(1, &BOB), amount_a); - }); - } -} diff --git a/examples/src/lib.rs b/examples/src/lib.rs index 5a5a681..4611d8a 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -1,4 +1,5 @@ mod expects; +mod fees; mod first_look; mod holding_modifiers; mod locks; diff --git a/examples/src/queries/mod.rs b/examples/src/queries/mod.rs index d99626f..bc6868a 100644 --- a/examples/src/queries/mod.rs +++ b/examples/src/queries/mod.rs @@ -105,7 +105,6 @@ mod tests { ]); assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); - print_para_events(); }); ParaA::execute_with(|| { diff --git a/examples/src/simple_test_net/mod.rs b/examples/src/simple_test_net/mod.rs index 6696489..2e1b768 100644 --- a/examples/src/simple_test_net/mod.rs +++ b/examples/src/simple_test_net/mod.rs @@ -130,7 +130,6 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { use parachain::{MsgQueue, Runtime, System}; let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - let other_para_ids = match para_id { 1 => [2, 3], 2 => [1, 3], @@ -139,7 +138,7 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { }; pallet_balances::GenesisConfig:: { - balances: vec![(ALICE, INITIAL_BALANCE), (relay_sovereign_account_id(), INITIAL_BALANCE)] + balances: vec![(ALICE, INITIAL_BALANCE), (relay_sovereign_account_id(), INITIAL_BALANCE), (BOB, INITIAL_BALANCE)] .into_iter() .chain(other_para_ids.iter().map( // Initial balance of native token for ALICE on all sibling sovereign accounts @@ -181,6 +180,7 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { .assimilate_storage(&mut t) .unwrap(); + let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| { sp_tracing::try_init_simple(); diff --git a/src/journey/fees/README.md b/src/journey/fees/README.md index d15b06f..7b382e5 100644 --- a/src/journey/fees/README.md +++ b/src/journey/fees/README.md @@ -91,22 +91,29 @@ This is useful in many cases: ```rust,noplayground let message = Xcm(vec![ - WithdrawAsset((Here, amount + fee_estimation).into()), + WithdrawAsset((Parent, message_fee).into()), BuyExecution { - fees: (Here, fee_estimation).into(), - weight_limit: WeightLimit::Limited(weight_estimation), + fees: (Parent, message_fee).into(), + weight_limit: WeightLimit::Unlimited, }, SetErrorHandler(Xcm(vec![ - RefundSurplus + RefundSurplus, + DepositAsset { + assets: All.into(), + beneficiary: AccountId32 { + network: Some(ByGenesis([0; 32])), + id: relay_sovereign_account_id().into(), + } + .into(), + }, ])), - DepositAsset { ... }, - DepositAsset { ... }, - DepositAsset { ... }, - DepositAsset { ... }, - DepositAsset { ... }, + Trap(1), + ClearOrigin, + ClearOrigin, + ClearOrigin, ]); ``` -In this example, we pay upfront for all the transactions we do later with the `DepositAsset` instructions. -If any transaction throws an error (for example, due to lack of funds), the error handler will be called and the weight for all the instructions that weren't executed is refunded. +In this example, we pay upfront for all the instructions in the XCM. +When the `Trap` instruction throws an error, the error handler will be called and the weight for all the instructions that weren't executed is refunded. For the full example, check our [repo](https://github.com/paritytech/xcm-docs/tree/main/examples). From c137e655524aebfad7017456cd1d35eb578ea007 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Mon, 5 Jun 2023 09:03:42 +0200 Subject: [PATCH 55/73] different structure in example folders --- .../{first_look.rs => 0_first_look/mod.rs} | 0 examples/src/{locks => 10_locks}/mod.rs | 0 .../src/{transfers => 1_transfers}/mod.rs | 0 .../src/{transfers => 1_transfers}/reserve.rs | 0 .../{transfers => 1_transfers}/teleport.rs | 0 examples/src/{fees => 2_fees}/mod.rs | 0 examples/src/{transact => 3_transact}/mod.rs | 0 examples/src/{origins => 4_origins}/mod.rs | 0 .../mod.rs | 0 .../mod.rs | 0 examples/src/{expects => 7_expects}/mod.rs | 0 examples/src/{queries => 8_queries}/mod.rs | 0 .../mod.rs | 0 examples/src/lib.rs | 27 +++++++++++++------ examples/src/simple_test_net/mod.rs | 2 +- 15 files changed, 20 insertions(+), 9 deletions(-) rename examples/src/{first_look.rs => 0_first_look/mod.rs} (100%) rename examples/src/{locks => 10_locks}/mod.rs (100%) rename examples/src/{transfers => 1_transfers}/mod.rs (100%) rename examples/src/{transfers => 1_transfers}/reserve.rs (100%) rename examples/src/{transfers => 1_transfers}/teleport.rs (100%) rename examples/src/{fees => 2_fees}/mod.rs (100%) rename examples/src/{transact => 3_transact}/mod.rs (100%) rename examples/src/{origins => 4_origins}/mod.rs (100%) rename examples/src/{holding_modifiers => 5_holding_modifiers}/mod.rs (100%) rename examples/src/{trap_and_claim => 6_trap_and_claim}/mod.rs (100%) rename examples/src/{expects => 7_expects}/mod.rs (100%) rename examples/src/{queries => 8_queries}/mod.rs (100%) rename examples/src/{version_subscription => 9_version_subscription}/mod.rs (100%) diff --git a/examples/src/first_look.rs b/examples/src/0_first_look/mod.rs similarity index 100% rename from examples/src/first_look.rs rename to examples/src/0_first_look/mod.rs diff --git a/examples/src/locks/mod.rs b/examples/src/10_locks/mod.rs similarity index 100% rename from examples/src/locks/mod.rs rename to examples/src/10_locks/mod.rs diff --git a/examples/src/transfers/mod.rs b/examples/src/1_transfers/mod.rs similarity index 100% rename from examples/src/transfers/mod.rs rename to examples/src/1_transfers/mod.rs diff --git a/examples/src/transfers/reserve.rs b/examples/src/1_transfers/reserve.rs similarity index 100% rename from examples/src/transfers/reserve.rs rename to examples/src/1_transfers/reserve.rs diff --git a/examples/src/transfers/teleport.rs b/examples/src/1_transfers/teleport.rs similarity index 100% rename from examples/src/transfers/teleport.rs rename to examples/src/1_transfers/teleport.rs diff --git a/examples/src/fees/mod.rs b/examples/src/2_fees/mod.rs similarity index 100% rename from examples/src/fees/mod.rs rename to examples/src/2_fees/mod.rs diff --git a/examples/src/transact/mod.rs b/examples/src/3_transact/mod.rs similarity index 100% rename from examples/src/transact/mod.rs rename to examples/src/3_transact/mod.rs diff --git a/examples/src/origins/mod.rs b/examples/src/4_origins/mod.rs similarity index 100% rename from examples/src/origins/mod.rs rename to examples/src/4_origins/mod.rs diff --git a/examples/src/holding_modifiers/mod.rs b/examples/src/5_holding_modifiers/mod.rs similarity index 100% rename from examples/src/holding_modifiers/mod.rs rename to examples/src/5_holding_modifiers/mod.rs diff --git a/examples/src/trap_and_claim/mod.rs b/examples/src/6_trap_and_claim/mod.rs similarity index 100% rename from examples/src/trap_and_claim/mod.rs rename to examples/src/6_trap_and_claim/mod.rs diff --git a/examples/src/expects/mod.rs b/examples/src/7_expects/mod.rs similarity index 100% rename from examples/src/expects/mod.rs rename to examples/src/7_expects/mod.rs diff --git a/examples/src/queries/mod.rs b/examples/src/8_queries/mod.rs similarity index 100% rename from examples/src/queries/mod.rs rename to examples/src/8_queries/mod.rs diff --git a/examples/src/version_subscription/mod.rs b/examples/src/9_version_subscription/mod.rs similarity index 100% rename from examples/src/version_subscription/mod.rs rename to examples/src/9_version_subscription/mod.rs diff --git a/examples/src/lib.rs b/examples/src/lib.rs index 4611d8a..f3d0caa 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -1,12 +1,23 @@ -mod expects; -mod fees; -mod first_look; -mod holding_modifiers; -mod locks; -mod origins; -mod queries; mod simple_test_net; -mod transact; +#[path ="0_first_look/mod.rs"] +mod first_look; +#[path="1_transfers/mod.rs"] mod transfers; +#[path ="2_fees/mod.rs"] +mod fees; +#[path ="3_transact/mod.rs"] +mod transact; +#[path ="4_origins/mod.rs"] +mod origins; +#[path ="5_holding_modifiers/mod.rs"] +mod holding_modifiers; +#[path ="6_trap_and_claim/mod.rs"] mod trap_and_claim; +#[path="7_expects/mod.rs"] +mod expects; +#[path ="8_queries/mod.rs"] +mod queries; +#[path ="9_version_subscription/mod.rs"] mod version_subscription; +#[path ="10_locks/mod.rs"] +mod locks; \ No newline at end of file diff --git a/examples/src/simple_test_net/mod.rs b/examples/src/simple_test_net/mod.rs index 2e1b768..917f21a 100644 --- a/examples/src/simple_test_net/mod.rs +++ b/examples/src/simple_test_net/mod.rs @@ -13,7 +13,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . - +#![allow(dead_code)] pub mod parachain; pub mod relay_chain; From 1ec3644002b4044b98c59fbec83507271a4e6a3c Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Tue, 6 Jun 2023 10:03:50 +0200 Subject: [PATCH 56/73] address feedback --- src/journey/transfers/teleports.md | 2 +- src/testing/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/journey/transfers/teleports.md b/src/journey/transfers/teleports.md index 6121100..7e1bb84 100644 --- a/src/journey/transfers/teleports.md +++ b/src/journey/transfers/teleports.md @@ -81,7 +81,7 @@ This instruction is a *trusted indication*. It should only be executed if the or This level of care must be taken because this instruction will *put assets into the circulating supply*, usually minting them. As specified earlier, this can result in an increase/decrease in circulating supply of an asset, or a duplication/loss of an NFT, if the source is not trusted for this purpose. -You can set which origins are allowed (act as teleporters) by configuring the [IsTeleporter](../../executor_config/index.md##isteleporter) type in the XCM executor. +You can set which origins are allowed to act as teleporters by configuring the [IsTeleporter](../../executor_config/index.md#isteleporter) type in the XCM executor. If the origin is not allowed to teleport assets to this system, an `UntrustedTeleportLocation` error is returned. This instruction will populate the holding register with the teleported assets, which can be used by further instructions. diff --git a/src/testing/README.md b/src/testing/README.md index 8b7d966..9a25358 100644 --- a/src/testing/README.md +++ b/src/testing/README.md @@ -14,7 +14,7 @@ The simulator mocks the Downward Message Passing pallet, enabling us to get the The simulator should be used as a XCM playground. For testing the configuration of your parachain and the integration with other chains, you can use the xcm-emulator. ## XCM Emulator -The [xcm-emulator](https://github.com/paritytech/cumulus/tree/master/xcm/xcm-emulator) is a tool to emulator XCM program execution using pre-configured runtimes, including those used to run on live networks, such as Kusama, Polkadot, Statemine et cetera. This allows for testing cross-chain message passing and verifying outcomes, weights, and side-effects. +The [xcm-emulator](https://github.com/paritytech/cumulus/tree/master/xcm/xcm-emulator) is a tool to emulate XCM program execution using pre-configured runtimes, including those used to run on live networks, such as Kusama, Polkadot, Statemine, etc. This allows for testing cross-chain message passing and verifying outcomes, weights, and side-effects. An example of how the emulator is used for testing common good parachains can be found [here](https://github.com/paritytech/cumulus/tree/master/parachains/integration-tests/emulated). From 8fd0555c9dcd4f4ffab13547ff9f7e85d54afefb Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 6 Jun 2023 16:21:51 +0800 Subject: [PATCH 57/73] Update interoperability.md --- src/overview/interoperability.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/overview/interoperability.md b/src/overview/interoperability.md index 3760425..4cac012 100644 --- a/src/overview/interoperability.md +++ b/src/overview/interoperability.md @@ -1,21 +1,21 @@ # Introduction -XCM is a messaging format, a language, designed to enable seamless communication between different consensus systems, for example blockchains and smart contracts. -XCM was originally developed for the [Polkadot](https://polkadot.network/) ecosystem, but was designed to be general enough to provide a common language for cross-consensus communication that can be used anywhere. +XCM is a messaging format, a language, designed to enable seamless communication between different consensus systems. Examples of consensus systems are blockchains and smart contracts. +XCM is originally developed for the [Polkadot](https://polkadot.network/) ecosystem, but is designed to be general enough to provide a common language for cross-consensus communication that can be used anywhere. XCM is a language in which interactions (programs) can be written. It aims to provide better interoperability between consensus systems, both more features and a better user and developer experience. Its goal is to let blockchain ecosystems thrive via specialization instead of generalization. -If there's no interoperability, a chain is forced to do everything on its own. -With XCM, a chain can specialize and do what it does best, while still getting the benefits from interacting with others. +If there's no interoperability, a chain is forced to host all services and support all functionalities on its own. +With XCM, we are able to achieve an ecosystem-wide division of labour: a chain can specialize and focus on its own business logic, and leverage the benefits of depending on other specialized blockchain for services that it does not provide. -XCM has four high-level core design principles which it stands to follow: -1. Asynchronous: XCM messages in no way assume that the sender will be blocking on its completion -2. Absolute: XCM messages are guaranteed to be delivered and interpreted accurately, in order and in a timely fashion. Once a message is sent, one can be sure it will be processed as it was intended to be. -3. Asymmetric: XCM messages, by default, do not have results that let the sender know that the message was received - they follow the 'fire and forget' paradigm. Any results must be separately communicated to the sender with an additional message back to the origin. +XCM has four high-level inherent design assumptions: +1. Asynchronous: XCMs in no way assume that the sender will be blocking on its completion +2. Absolute: XCMs are guaranteed to be delivered and interpreted accurately, in order and in a timely fashion. Once a message is sent, one can be sure it will be processed as it was intended to be. +3. Asymmetric: XCMs, by default, do not have results that let the sender know that the message was received - they follow the 'fire and forget' paradigm. Any results must be separately communicated to the sender with an additional message back to the origin. 4. Agnostic: XCM makes no assumptions about the nature of the consensus systems between which the messages are being passed. XCM as a message format should be usable in any system that derives finality through consensus. -XCM is a work-in-progress, the format is expected to change over time. +XCM is a work-in-progress; the format is expected to change over time. It has an RFC process to propose changes, which end up in newer versions, the current one being v3. To keep up with the development of the format, or to propose changes, go to [the XCM format repository](https://github.com/paritytech/xcm-format). From b0a72941ede6cb69e610ff83c61b23e4af5b2c27 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 6 Jun 2023 16:43:05 +0800 Subject: [PATCH 58/73] Update format.md --- src/overview/format.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/overview/format.md b/src/overview/format.md index da3ce0e..5a8a862 100644 --- a/src/overview/format.md +++ b/src/overview/format.md @@ -1,19 +1,18 @@ # A Format, Not a Protocol It's essential to understand that XCM is a format, not a protocol. -It describes how messages should be structured and contains instructions relevant to the on-chain actions the message intends to perform. +It describes how messages should be structured and contains instructions that convey on-chain actions that the message intends to perform. However, XCM does not dictate how messages are delivered. -That responsibility falls on [transport layer protocols](../transport_protocols/index.md) such as XCMP (Cross Chain Message Passing) and VMP (Vertical Message Passing) in the Polkadot ecosystem, or any others to come. +That responsibility falls on [transport layer protocols](../transport_protocols/index.md) such as XCMP (Cross Chain Message Passing) and VMP (Vertical Message Passing) in the Polkadot ecosystem, or any others to come, such as bridging protocols. This separation of concerns is useful, since it allows us to think of the interactions we want to build between systems without having to think about how the messages involved are actually routed. XCM is similar to how RESTful services use REST as an architectural style of development, where HTTP requests contain specific parameters to perform some action. -Similar to UDP, out of the box XCM is a "fire and forget" model, unless there is a separate XCM message designed to be a response message which can be sent from the recipient to the sender. All error handling should also be done on the recipient side. +Similar to UDP, out of the box XCM is a "fire and forget" model, unless there is a separate XCM designed to be a response message which can be sent from the recipient to the sender. All error handling should also be done on the recipient side. -XCM is not designed in a way where every system supporting the format is expected to be able to interpret any possible XCM message. Practically speaking, one can imagine that some messages will not have reasonable interpretations under some systems or will be intentionally unsupported. +XCM is not designed in a way where every system supporting the format is expected to be able to interpret any possible XCM. Practically speaking, one can imagine that some messages will not have reasonable interpretations under some systems or will be intentionally unsupported. -Furthermore, it's essential to realize that XCM messages by themselves are not considered transactions. XCM describes how to change the state of the target network, but the message by itself doesn't perform the state change. -This partly ties to what is called asynchronous composability, which allows XCM messages to bypass the concept of time-constrained mechanisms, like on-chain scheduling and execution over time in the correct order in which it was intended. +Furthermore, it's essential to realize that XCMs by themselves are not considered on-chain transactions: XCM describes how to change the state of the target consensus system, but the message by itself does not perform state changes. In short, XCM is a declarative language; the actual interpretation and behaviour of each instruction in an XCM is defined by target's XCVM implementation. XCM is a language in which rich interactions between systems can be written. Both simple and more complex scenarios can be expressed, and developers are encouraged to design and implement diverse cross-consensus communication solutions. From 4a2b1a0aca32b247aadd991f1c8c138d9abf5f42 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 6 Jun 2023 11:46:34 -0300 Subject: [PATCH 59/73] Fix suggestions --- examples/src/2_fees/mod.rs | 16 ++++++++-------- examples/src/lib.rs | 38 +++++++++++++++++++------------------- src/testing/README.md | 2 +- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/examples/src/2_fees/mod.rs b/examples/src/2_fees/mod.rs index 50fbbff..ba7e447 100644 --- a/examples/src/2_fees/mod.rs +++ b/examples/src/2_fees/mod.rs @@ -1,16 +1,16 @@ #[cfg(test)] mod tests { - use crate::simple_test_net::{*, parachain::estimate_message_fee}; + use crate::simple_test_net::{parachain::estimate_message_fee, *}; use frame_support::assert_ok; use xcm::latest::prelude::*; use xcm_simulator::TestExt; /// Scenario: - /// Relay chain sends a XCM to Parachain A. - /// Enough execution is bought for the full message. + /// Relay chain sends a XCM to Parachain A. + /// Enough execution is bought for the full message. /// However, the message errors at the Trap(1) instruction (simulates error in other instructions). - /// The last three instructions are not executed - /// and the weight surplus of these instructions is refunded to the relay chain account. + /// The last three instructions are not executed + /// and the weight surplus of these instructions is refunded to the relay chain account. #[test] fn refund_surplus() { MockNet::reset(); @@ -33,9 +33,9 @@ mod tests { }, ])), Trap(1), - ClearOrigin, - ClearOrigin, - ClearOrigin, + ClearOrigin, + ClearOrigin, + ClearOrigin, ]); Relay::execute_with(|| { diff --git a/examples/src/lib.rs b/examples/src/lib.rs index f3d0caa..1ac862b 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -1,23 +1,23 @@ -mod simple_test_net; -#[path ="0_first_look/mod.rs"] -mod first_look; -#[path="1_transfers/mod.rs"] -mod transfers; -#[path ="2_fees/mod.rs"] +#[path = "7_expects/mod.rs"] +mod expects; +#[path = "2_fees/mod.rs"] mod fees; -#[path ="3_transact/mod.rs"] -mod transact; -#[path ="4_origins/mod.rs"] -mod origins; -#[path ="5_holding_modifiers/mod.rs"] +#[path = "0_first_look/mod.rs"] +mod first_look; +#[path = "5_holding_modifiers/mod.rs"] mod holding_modifiers; -#[path ="6_trap_and_claim/mod.rs"] -mod trap_and_claim; -#[path="7_expects/mod.rs"] -mod expects; -#[path ="8_queries/mod.rs"] +#[path = "10_locks/mod.rs"] +mod locks; +#[path = "4_origins/mod.rs"] +mod origins; +#[path = "8_queries/mod.rs"] mod queries; -#[path ="9_version_subscription/mod.rs"] +mod simple_test_net; +#[path = "3_transact/mod.rs"] +mod transact; +#[path = "1_transfers/mod.rs"] +mod transfers; +#[path = "6_trap_and_claim/mod.rs"] +mod trap_and_claim; +#[path = "9_version_subscription/mod.rs"] mod version_subscription; -#[path ="10_locks/mod.rs"] -mod locks; \ No newline at end of file diff --git a/src/testing/README.md b/src/testing/README.md index 9a25358..c8364de 100644 --- a/src/testing/README.md +++ b/src/testing/README.md @@ -11,7 +11,7 @@ We'll discuss tools and best practices for each of these levels. The [xcm-simulator](https://github.com/paritytech/polkadot/tree/master/xcm/xcm-simulator) is a tool to quickly test the execution of various XCM instructions against the `xcm-executor`. The examples in this documentation use the xcm-simulator. The simulator mocks the Downward Message Passing pallet, enabling us to get the XCMs that a parachain receives from the relay chain using the `received_dmp` getter. -The simulator should be used as a XCM playground. For testing the configuration of your parachain and the integration with other chains, you can use the xcm-emulator. +The simulator should be used as a XCM playground. For testing the XCM configuration of your parachain and the integration with other chains, you can use the xcm-emulator. ## XCM Emulator The [xcm-emulator](https://github.com/paritytech/cumulus/tree/master/xcm/xcm-emulator) is a tool to emulate XCM program execution using pre-configured runtimes, including those used to run on live networks, such as Kusama, Polkadot, Statemine, etc. This allows for testing cross-chain message passing and verifying outcomes, weights, and side-effects. From def97078c53b6b4cb393641a0a18735d8d04f3dd Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 6 Jun 2023 11:48:38 -0300 Subject: [PATCH 60/73] Update src/testing/README.md Co-authored-by: Keith Yeung --- src/testing/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/testing/README.md b/src/testing/README.md index c8364de..9a96d31 100644 --- a/src/testing/README.md +++ b/src/testing/README.md @@ -18,7 +18,7 @@ The [xcm-emulator](https://github.com/paritytech/cumulus/tree/master/xcm/xcm-emu An example of how the emulator is used for testing common good parachains can be found [here](https://github.com/paritytech/cumulus/tree/master/parachains/integration-tests/emulated). -The xcm-emulator uses the transport layer pallets. However, the messages do not physically go through the same messaging infrastructure as on live networks, so some code is not being tested. Also, consensus related events can not be tested, like disputes, staking and iamonline events. To test for these events, parachains can use E2E tests. +The xcm-emulator uses the transport layer pallets. However, the messages do not utilize the same messaging infrastructure as live networks, as the transport mechanism is being mocked out. Also, consensus related events are not tested, like disputes, staking and iamonline events. To test for these events, parachains can use E2E tests. ## End-to-End testing There are two frameworks being used in the DotSama ecosystem to do e2e testing: From e66535198a90a9e5fc64fec5177c1a1d7d71357f Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 6 Jun 2023 11:48:50 -0300 Subject: [PATCH 61/73] Update src/testing/README.md Co-authored-by: Keith Yeung --- src/testing/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/testing/README.md b/src/testing/README.md index 9a96d31..c1db8ba 100644 --- a/src/testing/README.md +++ b/src/testing/README.md @@ -21,6 +21,6 @@ An example of how the emulator is used for testing common good parachains can be The xcm-emulator uses the transport layer pallets. However, the messages do not utilize the same messaging infrastructure as live networks, as the transport mechanism is being mocked out. Also, consensus related events are not tested, like disputes, staking and iamonline events. To test for these events, parachains can use E2E tests. ## End-to-End testing -There are two frameworks being used in the DotSama ecosystem to do e2e testing: +There are two frameworks being used in the ecosystem to do e2e testing: - [Zombienet](https://github.com/paritytech/zombienet). - [Chopsticks](https://github.com/AcalaNetwork/chopsticks). \ No newline at end of file From 855c6a18ac8f37ec1cf80c9dc01cb60ab6b6e872 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Wed, 7 Jun 2023 20:34:37 +0800 Subject: [PATCH 62/73] Update src/overview/interoperability.md Co-authored-by: Francisco Aguirre --- src/overview/interoperability.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/overview/interoperability.md b/src/overview/interoperability.md index 4cac012..3342f7a 100644 --- a/src/overview/interoperability.md +++ b/src/overview/interoperability.md @@ -1,6 +1,6 @@ # Introduction -XCM is a messaging format, a language, designed to enable seamless communication between different consensus systems. Examples of consensus systems are blockchains and smart contracts. +XCM is a messaging format, designed to enable seamless communication between different consensus systems. Examples of consensus systems are blockchains and smart contracts. XCM is originally developed for the [Polkadot](https://polkadot.network/) ecosystem, but is designed to be general enough to provide a common language for cross-consensus communication that can be used anywhere. XCM is a language in which interactions (programs) can be written. From 62f2ede45996e5a662363c6ff65b979f100ff55d Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Wed, 7 Jun 2023 20:34:49 +0800 Subject: [PATCH 63/73] Update src/overview/interoperability.md Co-authored-by: Francisco Aguirre --- src/overview/interoperability.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/overview/interoperability.md b/src/overview/interoperability.md index 3342f7a..e5394d1 100644 --- a/src/overview/interoperability.md +++ b/src/overview/interoperability.md @@ -1,7 +1,7 @@ # Introduction XCM is a messaging format, designed to enable seamless communication between different consensus systems. Examples of consensus systems are blockchains and smart contracts. -XCM is originally developed for the [Polkadot](https://polkadot.network/) ecosystem, but is designed to be general enough to provide a common language for cross-consensus communication that can be used anywhere. +XCM comes from the [Polkadot](https://polkadot.network/) ecosystem, but is designed to be general enough to provide a common language for cross-consensus communication that can be used anywhere. XCM is a language in which interactions (programs) can be written. It aims to provide better interoperability between consensus systems, both more features and a better user and developer experience. From 7bc4625b2a644564a302cc478859b20bf19a67d3 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Wed, 7 Jun 2023 20:35:01 +0800 Subject: [PATCH 64/73] Update src/overview/interoperability.md Co-authored-by: Francisco Aguirre --- src/overview/interoperability.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/overview/interoperability.md b/src/overview/interoperability.md index e5394d1..812966b 100644 --- a/src/overview/interoperability.md +++ b/src/overview/interoperability.md @@ -3,8 +3,6 @@ XCM is a messaging format, designed to enable seamless communication between different consensus systems. Examples of consensus systems are blockchains and smart contracts. XCM comes from the [Polkadot](https://polkadot.network/) ecosystem, but is designed to be general enough to provide a common language for cross-consensus communication that can be used anywhere. -XCM is a language in which interactions (programs) can be written. -It aims to provide better interoperability between consensus systems, both more features and a better user and developer experience. Its goal is to let blockchain ecosystems thrive via specialization instead of generalization. If there's no interoperability, a chain is forced to host all services and support all functionalities on its own. From 252a16a422153c946ff5f4ef46edf4c559f0c09f Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Wed, 7 Jun 2023 20:38:10 +0800 Subject: [PATCH 65/73] Update src/overview/interoperability.md --- src/overview/interoperability.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/overview/interoperability.md b/src/overview/interoperability.md index 812966b..480e784 100644 --- a/src/overview/interoperability.md +++ b/src/overview/interoperability.md @@ -10,7 +10,7 @@ With XCM, we are able to achieve an ecosystem-wide division of labour: a chain c XCM has four high-level inherent design assumptions: 1. Asynchronous: XCMs in no way assume that the sender will be blocking on its completion -2. Absolute: XCMs are guaranteed to be delivered and interpreted accurately, in order and in a timely fashion. Once a message is sent, one can be sure it will be processed as it was intended to be. +2. Absolute: XCMs are assumed to be delivered and interpreted accurately, in order and in a timely fashion. Once a message is sent, one can assume that it will be processed as intended. 3. Asymmetric: XCMs, by default, do not have results that let the sender know that the message was received - they follow the 'fire and forget' paradigm. Any results must be separately communicated to the sender with an additional message back to the origin. 4. Agnostic: XCM makes no assumptions about the nature of the consensus systems between which the messages are being passed. XCM as a message format should be usable in any system that derives finality through consensus. From 869bcdeb55007b868d4dcfa2cb6ef04313818aa7 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 21 Jun 2023 14:40:44 +0100 Subject: [PATCH 66/73] Add glossary (#32) * Add glossary * Remove questionable item from glossary --- src/SUMMARY.md | 2 +- src/reference/glossary.md | 81 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/reference/glossary.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index ffdf6c6..092de67 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -40,4 +40,4 @@ - [All XCVM Registers](reference/xcvm-registers.md) - +- [Glossary](reference/glossary.md) diff --git a/src/reference/glossary.md b/src/reference/glossary.md new file mode 100644 index 0000000..c7bed58 --- /dev/null +++ b/src/reference/glossary.md @@ -0,0 +1,81 @@ +# Glossary + +## XCM (Cross-Consensus Messaging) + +A messaging format meant to communicate intentions between consensus systems. +XCM could also refer to a single message. + +## Instructions + +XCMs are composed of a sequence of instructions. +Each instruction aims to convey a particular intention. +There are instructions for transferring and locking assets, handling fees, calling arbitrary blobs, and more. + +## Consensus system + +A system that can reach any kind of consensus. +For example, relay chains, parachains, smart contracts. + +## MultiLocation + +A way of addressing consensus systems. +These could be relative or absolute. + +## Junction + +The different ways of descending down a `MultiLocation` hierarchy. +A junction can be a Parachain, an Account, or more. + +## MultiAsset + +A way of identifying assets in the same or another consensus system, by using a `MultiLocation`. + +## Sovereign account + +An account on a consensus system that is controlled by an account in another consensus system. + +## Teleport + +A way of transferring assets between two consensus systems without the need of a third party. +It consists of the sender system burning the asset that wants to be sent over and the recipient minting an equivalent amount of that asset. +It requires a lot of trust between the two systems, since failure to mint or burn will reduce the total issuance of the token. + +## Reserve asset transfer + +A way of transferring assets between two consensus systems that don't trust each other, by using a third system they both trust, called the reserve. +The real asset only exists on the reserve, both sender and recipient only deal with derivatives. +It consists of the sender burning a certain amount of derivatives, telling the reserve to move real assets from its sovereign account to the destination's sovereign account, and then telling the recipient to mint the right amount of derivatives. + +## XCVM + +The virtual machine behind XCM. +Every XCM is an XCVM programme. +Holds state in registers. + +## Holding register + +An XCVM register used to hold arbitrary `Asset`s during the execution of an XCVM programme. + +## Barrier + +An XCM executor configuration item that works as a firewall for incoming XCMs. +All XCMs have to pass the barrier to be executed, else they are dropped. +It can be used for whitelisting only certain types or messages or messages from certain senders. + +## UMP (Upward Message Passing) + +Transport-layer protocol that allows parachains to send messages upwards to their relay chain. + +## DMP (Downward Message Passing) + +Transport-layer protocol that allows the relay chain to send messages downwards to one of their parachains. + +## XCMP (Cross-Consensus Message Passing) + +Transport-layer protocol that allows parachains to send messages between themselves, without going through the relay chain. + +## HRMP (Horizontal Message Passing) + +Transport-layer protocol that allows a parachain to send messages to a sibling parachain going through the relay chain. +It's a precursor to XCMP, also known as XCMP-lite. +It uses a mixture of UMP and VMP. From a0403869f071ce44df5ebcc104b05782515e3111 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 5 Jul 2023 15:26:41 +0200 Subject: [PATCH 67/73] Move shared mock_msg_queue to it's own file --- examples/src/simple_test_net/asset_hub.rs | 142 +-------------- .../src/simple_test_net/mock_msg_queue.rs | 172 ++++++++++++++++++ examples/src/simple_test_net/mod.rs | 27 +-- examples/src/simple_test_net/parachain.rs | 155 +--------------- 4 files changed, 195 insertions(+), 301 deletions(-) create mode 100644 examples/src/simple_test_net/mock_msg_queue.rs diff --git a/examples/src/simple_test_net/asset_hub.rs b/examples/src/simple_test_net/asset_hub.rs index 0937fc7..da9549a 100644 --- a/examples/src/simple_test_net/asset_hub.rs +++ b/examples/src/simple_test_net/asset_hub.rs @@ -16,7 +16,7 @@ //! Asset hub parachain runtime mock. -use super::{Balance, ForeignChainAliasAccount, UNITS}; +use super::{mock_msg_queue::pallet as mock_msg_queue, Balance, ForeignChainAliasAccount, UNITS}; use codec::{Decode, Encode}; use core::marker::PhantomData; use frame_support::{ @@ -324,146 +324,6 @@ impl Config for XcmConfig { type SafeCallFilter = Everything; } -#[frame_support::pallet] -pub mod mock_msg_queue { - use super::*; - use frame_support::pallet_prelude::*; - - #[pallet::config] - pub trait Config: frame_system::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - type XcmExecutor: ExecuteXcm; - } - - #[pallet::call] - impl Pallet {} - - #[pallet::pallet] - #[pallet::without_storage_info] - pub struct Pallet(_); - - #[pallet::storage] - #[pallet::getter(fn parachain_id)] - pub(super) type ParachainId = StorageValue<_, ParaId, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn received_dmp)] - /// A queue of received DMP messages - pub(super) type ReceivedDmp = StorageValue<_, Vec>, ValueQuery>; - - impl Get for Pallet { - fn get() -> ParaId { - Self::parachain_id() - } - } - - pub type MessageId = [u8; 32]; - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - // XCMP - /// Some XCM was executed OK. - Success(Option), - /// Some XCM failed. - Fail(Option, XcmError), - /// Bad XCM version used. - BadVersion(Option), - /// Bad XCM format used. - BadFormat(Option), - - // DMP - /// Downward message is invalid XCM. - InvalidFormat(MessageId), - /// Downward message is unsupported version of XCM. - UnsupportedVersion(MessageId), - /// Downward message executed with the given outcome. - ExecutedDownward(MessageId, Outcome), - } - - impl Pallet { - pub fn set_para_id(para_id: ParaId) { - ParachainId::::put(para_id); - } - - fn handle_xcmp_message( - sender: ParaId, - _sent_at: RelayBlockNumber, - xcm: VersionedXcm, - max_weight: Weight, - ) -> Result { - let hash = Encode::using_encoded(&xcm, T::Hashing::hash); - let message_hash = Encode::using_encoded(&xcm, sp_io::hashing::blake2_256); - let (result, event) = match Xcm::::try_from(xcm) { - Ok(xcm) => { - 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 - // we just report the weight used. - Outcome::Incomplete(w, e) => (Ok(w), Event::Fail(Some(hash), e)), - } - }, - Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))), - }; - Self::deposit_event(event); - result - } - } - - impl XcmpMessageHandler for Pallet { - fn handle_xcmp_messages<'a, I: Iterator>( - iter: I, - max_weight: Weight, - ) -> Weight { - for (sender, sent_at, data) in iter { - let mut data_ref = data; - let _ = XcmpMessageFormat::decode(&mut data_ref) - .expect("Simulator encodes with versioned xcm format; qed"); - - let mut remaining_fragments = &data_ref[..]; - while !remaining_fragments.is_empty() { - if let Ok(xcm) = - VersionedXcm::::decode(&mut remaining_fragments) - { - let _ = Self::handle_xcmp_message(sender, sent_at, xcm, max_weight); - } else { - debug_assert!(false, "Invalid incoming XCMP message data"); - } - } - } - max_weight - } - } - - impl DmpMessageHandler for Pallet { - fn handle_dmp_messages( - iter: impl Iterator)>, - limit: Weight, - ) -> Weight { - for (_i, (_sent_at, data)) in iter.enumerate() { - let id = sp_io::hashing::blake2_256(&data[..]); - let maybe_versioned = VersionedXcm::::decode(&mut &data[..]); - match maybe_versioned { - Err(_) => { - Self::deposit_event(Event::InvalidFormat(id)); - }, - 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)); - }, - }, - } - } - limit - } - } -} - impl mock_msg_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type XcmExecutor = XcmExecutor; diff --git a/examples/src/simple_test_net/mock_msg_queue.rs b/examples/src/simple_test_net/mock_msg_queue.rs new file mode 100644 index 0000000..21d5c67 --- /dev/null +++ b/examples/src/simple_test_net/mock_msg_queue.rs @@ -0,0 +1,172 @@ +// Copyright 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 . + +//! Parachain runtime mock. + +use codec::{Decode, Encode}; + +use frame_support::weights::Weight; +use polkadot_parachain::primitives::{ + DmpMessageHandler, Id as ParaId, XcmpMessageFormat, XcmpMessageHandler, +}; +use polkadot_primitives::BlockNumber as RelayBlockNumber; +use sp_runtime::traits::{Get, Hash}; + +use sp_std::prelude::*; +use xcm::{latest::prelude::*, VersionedXcm}; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type XcmExecutor: ExecuteXcm; + } + + #[pallet::call] + impl Pallet {} + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::storage] + #[pallet::getter(fn parachain_id)] + pub(super) type ParachainId = StorageValue<_, ParaId, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn received_dmp)] + /// A queue of received DMP messages + pub(super) type ReceivedDmp = StorageValue<_, Vec>, ValueQuery>; + + impl Get for Pallet { + fn get() -> ParaId { + Self::parachain_id() + } + } + + pub type MessageId = [u8; 32]; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + // XCMP + /// Some XCM was executed OK. + Success(Option), + /// Some XCM failed. + Fail(Option, XcmError), + /// Bad XCM version used. + BadVersion(Option), + /// Bad XCM format used. + BadFormat(Option), + + // DMP + /// Downward message is invalid XCM. + InvalidFormat(MessageId), + /// Downward message is unsupported version of XCM. + UnsupportedVersion(MessageId), + /// Downward message executed with the given outcome. + ExecutedDownward(MessageId, Outcome), + } + + impl Pallet { + pub fn set_para_id(para_id: ParaId) { + ParachainId::::put(para_id); + } + + fn handle_xcmp_message( + sender: ParaId, + _sent_at: RelayBlockNumber, + xcm: VersionedXcm, + max_weight: Weight, + ) -> Result { + let hash = Encode::using_encoded(&xcm, T::Hashing::hash); + let message_hash = Encode::using_encoded(&xcm, sp_io::hashing::blake2_256); + let (result, event) = match Xcm::::try_from(xcm) { + Ok(xcm) => { + 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 + // we just report the weight used. + Outcome::Incomplete(w, e) => (Ok(w), Event::Fail(Some(hash), e)), + } + } + Err(()) => ( + Err(XcmError::UnhandledXcmVersion), + Event::BadVersion(Some(hash)), + ), + }; + Self::deposit_event(event); + result + } + } + + impl XcmpMessageHandler for Pallet { + fn handle_xcmp_messages<'a, I: Iterator>( + iter: I, + max_weight: Weight, + ) -> Weight { + for (sender, sent_at, data) in iter { + let mut data_ref = data; + let _ = XcmpMessageFormat::decode(&mut data_ref) + .expect("Simulator encodes with versioned xcm format; qed"); + + let mut remaining_fragments = &data_ref[..]; + while !remaining_fragments.is_empty() { + if let Ok(xcm) = + VersionedXcm::::decode(&mut remaining_fragments) + { + let _ = Self::handle_xcmp_message(sender, sent_at, xcm, max_weight); + } else { + debug_assert!(false, "Invalid incoming XCMP message data"); + } + } + } + max_weight + } + } + + impl DmpMessageHandler for Pallet { + fn handle_dmp_messages( + iter: impl Iterator)>, + limit: Weight, + ) -> Weight { + for (_i, (_sent_at, data)) in iter.enumerate() { + let id = sp_io::hashing::blake2_256(&data[..]); + let maybe_versioned = VersionedXcm::::decode(&mut &data[..]); + match maybe_versioned { + Err(_) => { + Self::deposit_event(Event::InvalidFormat(id)); + } + 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)); + } + }, + } + } + limit + } + } +} diff --git a/examples/src/simple_test_net/mod.rs b/examples/src/simple_test_net/mod.rs index 917f21a..5d96f76 100644 --- a/examples/src/simple_test_net/mod.rs +++ b/examples/src/simple_test_net/mod.rs @@ -138,17 +138,21 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { }; pallet_balances::GenesisConfig:: { - balances: vec![(ALICE, INITIAL_BALANCE), (relay_sovereign_account_id(), INITIAL_BALANCE), (BOB, INITIAL_BALANCE)] - .into_iter() - .chain(other_para_ids.iter().map( - // Initial balance of native token for ALICE on all sibling sovereign accounts - |¶_id| (sibling_account_sovereign_account_id(para_id, ALICE), INITIAL_BALANCE), - )) - .chain(other_para_ids.iter().map( - // Initial balance of native token all sibling sovereign accounts - |¶_id| (sibling_sovereign_account_id(para_id), INITIAL_BALANCE), - )) - .collect(), + balances: vec![ + (ALICE, INITIAL_BALANCE), + (relay_sovereign_account_id(), INITIAL_BALANCE), + (BOB, INITIAL_BALANCE), + ] + .into_iter() + .chain(other_para_ids.iter().map( + // Initial balance of native token for ALICE on all sibling sovereign accounts + |¶_id| (sibling_account_sovereign_account_id(para_id, ALICE), INITIAL_BALANCE), + )) + .chain(other_para_ids.iter().map( + // Initial balance of native token all sibling sovereign accounts + |¶_id| (sibling_sovereign_account_id(para_id), INITIAL_BALANCE), + )) + .collect(), } .assimilate_storage(&mut t) .unwrap(); @@ -180,7 +184,6 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { .assimilate_storage(&mut t) .unwrap(); - let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| { sp_tracing::try_init_simple(); diff --git a/examples/src/simple_test_net/parachain.rs b/examples/src/simple_test_net/parachain.rs index 2274ccf..54d604f 100644 --- a/examples/src/simple_test_net/parachain.rs +++ b/examples/src/simple_test_net/parachain.rs @@ -16,8 +16,10 @@ //! Parachain runtime mock. -use super::{AllowNoteUnlockables, Balance, ForeignChainAliasAccount}; -use codec::{Decode, Encode}; +use super::{ + mock_msg_queue::pallet as mock_msg_queue, AllowNoteUnlockables, Balance, + ForeignChainAliasAccount, +}; use core::marker::PhantomData; use frame_support::{ construct_runtime, ensure, parameter_types, @@ -32,19 +34,16 @@ use frame_support::{ }; use frame_system::{EnsureRoot, EnsureSigned}; use pallet_xcm::XcmPassthrough; -use polkadot_parachain::primitives::{ - DmpMessageHandler, Id as ParaId, Sibling, XcmpMessageFormat, XcmpMessageHandler, -}; -use polkadot_primitives::BlockNumber as RelayBlockNumber; +use polkadot_parachain::primitives::Sibling; use sp_core::{ConstU128, ConstU32, H256}; use sp_runtime::{ testing::Header, - traits::{Get, Hash, IdentityLookup}, + traits::{Get, IdentityLookup}, AccountId32, }; use sp_std::{cell::RefCell, prelude::*}; -use xcm::{latest::prelude::*, VersionedXcm}; +use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, CurrencyAdapter as XcmCurrencyAdapter, @@ -378,146 +377,6 @@ impl Config for XcmConfig { type SafeCallFilter = Everything; } -#[frame_support::pallet] -pub mod mock_msg_queue { - use super::*; - use frame_support::pallet_prelude::*; - - #[pallet::config] - pub trait Config: frame_system::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - type XcmExecutor: ExecuteXcm; - } - - #[pallet::call] - impl Pallet {} - - #[pallet::pallet] - #[pallet::without_storage_info] - pub struct Pallet(_); - - #[pallet::storage] - #[pallet::getter(fn parachain_id)] - pub(super) type ParachainId = StorageValue<_, ParaId, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn received_dmp)] - /// A queue of received DMP messages - pub(super) type ReceivedDmp = StorageValue<_, Vec>, ValueQuery>; - - impl Get for Pallet { - fn get() -> ParaId { - Self::parachain_id() - } - } - - pub type MessageId = [u8; 32]; - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - // XCMP - /// Some XCM was executed OK. - Success(Option), - /// Some XCM failed. - Fail(Option, XcmError), - /// Bad XCM version used. - BadVersion(Option), - /// Bad XCM format used. - BadFormat(Option), - - // DMP - /// Downward message is invalid XCM. - InvalidFormat(MessageId), - /// Downward message is unsupported version of XCM. - UnsupportedVersion(MessageId), - /// Downward message executed with the given outcome. - ExecutedDownward(MessageId, Outcome), - } - - impl Pallet { - pub fn set_para_id(para_id: ParaId) { - ParachainId::::put(para_id); - } - - fn handle_xcmp_message( - sender: ParaId, - _sent_at: RelayBlockNumber, - xcm: VersionedXcm, - max_weight: Weight, - ) -> Result { - let hash = Encode::using_encoded(&xcm, T::Hashing::hash); - let message_hash = Encode::using_encoded(&xcm, sp_io::hashing::blake2_256); - let (result, event) = match Xcm::::try_from(xcm) { - Ok(xcm) => { - 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 - // we just report the weight used. - Outcome::Incomplete(w, e) => (Ok(w), Event::Fail(Some(hash), e)), - } - }, - Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))), - }; - Self::deposit_event(event); - result - } - } - - impl XcmpMessageHandler for Pallet { - fn handle_xcmp_messages<'a, I: Iterator>( - iter: I, - max_weight: Weight, - ) -> Weight { - for (sender, sent_at, data) in iter { - let mut data_ref = data; - let _ = XcmpMessageFormat::decode(&mut data_ref) - .expect("Simulator encodes with versioned xcm format; qed"); - - let mut remaining_fragments = &data_ref[..]; - while !remaining_fragments.is_empty() { - if let Ok(xcm) = - VersionedXcm::::decode(&mut remaining_fragments) - { - let _ = Self::handle_xcmp_message(sender, sent_at, xcm, max_weight); - } else { - debug_assert!(false, "Invalid incoming XCMP message data"); - } - } - } - max_weight - } - } - - impl DmpMessageHandler for Pallet { - fn handle_dmp_messages( - iter: impl Iterator)>, - limit: Weight, - ) -> Weight { - for (_i, (_sent_at, data)) in iter.enumerate() { - let id = sp_io::hashing::blake2_256(&data[..]); - let maybe_versioned = VersionedXcm::::decode(&mut &data[..]); - match maybe_versioned { - Err(_) => { - Self::deposit_event(Event::InvalidFormat(id)); - }, - 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)); - }, - }, - } - } - limit - } - } -} - impl mock_msg_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type XcmExecutor = XcmExecutor; From 4d037fb1d92cdb2f63f845c75db2bcec8e22f9c3 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 5 Jul 2023 15:31:13 +0200 Subject: [PATCH 68/73] fix --- examples/src/simple_test_net/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/src/simple_test_net/mod.rs b/examples/src/simple_test_net/mod.rs index 5d96f76..6a1004b 100644 --- a/examples/src/simple_test_net/mod.rs +++ b/examples/src/simple_test_net/mod.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . #![allow(dead_code)] +pub mod mock_msg_queue; pub mod parachain; pub mod relay_chain; From 3f9bf72ea489c7b48e0c827cebd3288a2ee0992a Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 5 Jul 2023 17:20:02 +0200 Subject: [PATCH 69/73] Bump release version --- examples/Cargo.toml | 81 +++++++++++---------- examples/src/simple_test_net/mod.rs | 6 +- examples/src/simple_test_net/parachain.rs | 2 + examples/src/simple_test_net/relay_chain.rs | 70 ++++++++++++++---- 4 files changed, 102 insertions(+), 57 deletions(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 81a7e40..7a5158f 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -16,51 +16,52 @@ hex-literal = { version = "0.3.1" } libsecp256k1 = { version = "0.7" } #Polkadot -polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } -xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } -xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } -xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } -pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } -pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42"} -pallet-assets = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -pallet-uniques = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -pallet-nfts = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } -polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } -kusama-runtime = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } -kusama-runtime-constants = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } -xcm-simulator = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" } +polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.43" } +xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.43" } +xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.43" } +xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.43" } +pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.43" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +pallet-assets = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +pallet-uniques = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +pallet-nfts = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +pallet-message-queue = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.43" } +polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.43" } +kusama-runtime = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.43" } +kusama-runtime-constants = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.43" } +xcm-simulator = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.43" } # substrate -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -sp-session = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -sp-version = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +sp-session = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +sp-version = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } -cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } -cumulus-primitives-utility = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } -cumulus-primitives-timestamp = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } -cumulus-pallet-parachain-system = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } -cumulus-pallet-dmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } -cumulus-pallet-xcmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } -cumulus-pallet-xcm = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } -parachain-info = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } -cumulus-primitives-parachain-inherent = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } -cumulus-test-relay-sproof-builder = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } -statemine-runtime = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" } +cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.43" } +cumulus-primitives-utility = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.43" } +cumulus-primitives-timestamp = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.43" } +cumulus-pallet-parachain-system = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.43" } +cumulus-pallet-dmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.43" } +cumulus-pallet-xcmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.43" } +cumulus-pallet-xcm = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.43" } +parachain-info = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.43" } +cumulus-primitives-parachain-inherent = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.43" } +cumulus-test-relay-sproof-builder = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.43" } +statemine-runtime = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.43" } [dev-dependencies] env_logger = "0.9.0" log = "0.4.17" -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } -sp-trie = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +sp-trie = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } diff --git a/examples/src/simple_test_net/mod.rs b/examples/src/simple_test_net/mod.rs index 6a1004b..55e0bf1 100644 --- a/examples/src/simple_test_net/mod.rs +++ b/examples/src/simple_test_net/mod.rs @@ -29,7 +29,7 @@ use frame_support::{ use sp_core::blake2_256; use xcm::prelude::*; use xcm_executor::traits::{Convert, ShouldExecute}; -use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; +use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain, TestExt}; // Accounts pub const ADMIN: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0u8; 32]); @@ -72,7 +72,11 @@ decl_test_parachain! { decl_test_relay_chain! { pub struct Relay { Runtime = relay_chain::Runtime, + RuntimeCall = relay_chain::RuntimeCall, + RuntimeEvent = relay_chain::RuntimeEvent, XcmConfig = relay_chain::XcmConfig, + MessageQueue = relay_chain::MessageQueue, + System = relay_chain::System, new_ext = relay_ext(), } } diff --git a/examples/src/simple_test_net/parachain.rs b/examples/src/simple_test_net/parachain.rs index 54d604f..fdf9fcf 100644 --- a/examples/src/simple_test_net/parachain.rs +++ b/examples/src/simple_test_net/parachain.rs @@ -419,6 +419,8 @@ impl pallet_xcm::Config for Runtime { type TrustedLockers = TrustedLockerCase; type SovereignAccountOf = SovereignAccountOf; type MaxLockers = ConstU32<8>; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); type WeightInfo = pallet_xcm::TestWeightInfo; #[cfg(feature = "runtime-benchmarks")] type ReachableDest = ReachableDest; diff --git a/examples/src/simple_test_net/relay_chain.rs b/examples/src/simple_test_net/relay_chain.rs index eccf084..6e50b7b 100644 --- a/examples/src/simple_test_net/relay_chain.rs +++ b/examples/src/simple_test_net/relay_chain.rs @@ -30,17 +30,20 @@ use sp_core::{ConstU128, ConstU32, H256}; use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; use polkadot_parachain::primitives::Id as ParaId; -use polkadot_runtime_parachains::{configuration, origin, shared, ump}; +use polkadot_runtime_parachains::{configuration, origin, shared}; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, - AsPrefixedGeneralIndex, ChildParachainAsNative, ChildParachainConvertsVia, - ChildSystemParachainAsSuperuser, ConvertedConcreteId, CurrencyAdapter as XcmCurrencyAdapter, - FixedRateOfFungible, FixedWeightBounds, IsConcrete, NoChecking, NonFungiblesAdapter, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, WithComputedOrigin, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowSubscriptionsFrom, + AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, ChildParachainAsNative, + ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, ConvertedConcreteId, + CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete, + NoChecking, NonFungiblesAdapter, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, WithComputedOrigin, }; use xcm_executor::{traits::JustTry, Config, XcmExecutor}; +use xcm_simulator::{ + AggregateMessageOrigin, ProcessMessage, ProcessMessageError, UmpQueueId, WeightMeter, +}; use super::{AllowNoteUnlockables, AllowUnlocks, Balance, ForeignChainAliasAccount}; @@ -200,6 +203,7 @@ pub type Barrier = WithComputedOrigin< AllowUnlocks, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, + AllowSubscriptionsFrom, ), UniversalLocation, ConstU32<1>, @@ -260,6 +264,8 @@ impl pallet_xcm::Config for Runtime { type TrustedLockers = (); type SovereignAccountOf = SovereignAccountOf; type MaxLockers = ConstU32<8>; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); type WeightInfo = pallet_xcm::TestWeightInfo; #[cfg(feature = "runtime-benchmarks")] type ReachableDest = ReachableDest; @@ -270,19 +276,51 @@ parameter_types! { pub const FirstMessageFactorPercent: u64 = 100; } -impl ump::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type UmpSink = ump::XcmSink, Runtime>; - type FirstMessageFactorPercent = FirstMessageFactorPercent; - type ExecuteOverweightOrigin = frame_system::EnsureRoot; - type WeightInfo = ump::TestWeightInfo; -} - impl origin::Config for Runtime {} type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; +parameter_types! { + /// Amount of weight that can be spent per block to service messages. + pub MessageQueueServiceWeight: Weight = Weight::from_parts(1_000_000_000, 1_000_000); + pub const MessageQueueHeapSize: u32 = 65_536; + pub const MessageQueueMaxStale: u32 = 16; +} + +/// Message processor to handle any messages that were enqueued into the `MessageQueue` pallet. +pub struct MessageProcessor; +impl ProcessMessage for MessageProcessor { + type Origin = AggregateMessageOrigin; + + fn process_message( + message: &[u8], + origin: Self::Origin, + meter: &mut WeightMeter, + id: &mut [u8; 32], + ) -> Result { + let para = match origin { + AggregateMessageOrigin::Ump(UmpQueueId::Para(para)) => para, + }; + xcm_builder::ProcessXcmMessage::< + Junction, + xcm_executor::XcmExecutor, + RuntimeCall, + >::process_message(message, Junction::Parachain(para.into()), meter, id) + } +} + +impl pallet_message_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Size = u32; + type HeapSize = MessageQueueHeapSize; + type MaxStale = MessageQueueMaxStale; + type ServiceWeight = MessageQueueServiceWeight; + type MessageProcessor = MessageProcessor; + type QueueChangeHandler = (); + type WeightInfo = (); +} + construct_runtime!( pub enum Runtime where Block = Block, @@ -293,7 +331,7 @@ construct_runtime!( Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Uniques: pallet_uniques, ParasOrigin: origin::{Pallet, Origin}, - ParasUmp: ump::{Pallet, Call, Storage, Event}, XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin}, + MessageQueue: pallet_message_queue::{Pallet, Event}, } ); From df4f3b603eeed9c72a37adcdcc11d499654324f2 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 17 Jul 2023 14:24:04 -0300 Subject: [PATCH 70/73] Address feedback on writing (#36) * Address feedback on writing * More wording fixes * More wording fixes * Address feedback on pallet-xcm --- src/overview/README.md | 26 ++++++++++---------------- src/overview/architecture.md | 19 ++++++++++--------- src/overview/format.md | 15 +++++++-------- src/overview/interoperability.md | 19 ++++++++++--------- src/overview/xcvm.md | 20 ++++++++++---------- src/xcm.md | 23 ++++++++++++++++++----- 6 files changed, 65 insertions(+), 57 deletions(-) diff --git a/src/overview/README.md b/src/overview/README.md index 4bd40f6..53f38a7 100644 --- a/src/overview/README.md +++ b/src/overview/README.md @@ -1,21 +1,15 @@ # Overview -XCM allows for different consensus systems to communicate with each other. -This allows things like: -- Sending tokens from one chain to another -- Locking assets on one chain in order to gain some benefit on a smart contract on another chain -- Calling functions (extrinsics) on another chain +XCM enables different consensus systems to communicate with each other. +Common cross-consensus use-cases include: +- Sending tokens between blockchains +- Locking assets on one blockchain in order to gain some benefit on a smart contract on another blockchain +- Calling specific functions on another blockchain -But that's just the beginning. -The true power of XCM comes from its composability. -Once you can communicate with other consensus systems, you can get creative and implement whatever use case you need. -This is especially true in the context of an ecosystem of highly specialized chains, like Polkadot. +These are just a few basic examples; once you can communicate with other consensus systems, you can create applications that can leverage multiple blockchains' capabilities. +The potential it provides is especially evident in an ecosystem of highly specialized blockchains like Polkadot. -Decentralized distributed systems are very complex, so when building interactions between them, it's easy to make mistakes. -Because of that, the end-user is not expected to write custom XCMs from scratch for all the interactions they want to achieve. -Instead, builders will use XCM to create enticing products that provide a good and safe user experience. -This is usually done by carefully thinking and testing the interaction, then packaging it into your system's runtime logic (via an extrinsic or smart contract for example), and exposing that functionality to users. +Decentralized distributed systems are very complex, so it's easy to make errors when building interactions between them. +XCM is meant to be used by developers to package these interactions into their runtime logic before exposing that functionality to end users. -In this chapter, we will cover what XCM is, what it isn't, why it matters, and delve into the different components that make up the XCM ecosystem. - -Let's begin. +This chapter will cover what XCM is, what it isn't, and why it matters before exploring the different components that make up the XCM ecosystem. diff --git a/src/overview/architecture.md b/src/overview/architecture.md index 8f45b3b..9ec0e05 100644 --- a/src/overview/architecture.md +++ b/src/overview/architecture.md @@ -1,17 +1,18 @@ # Architecture -XCM is a [format](https://github.com/paritytech/xcm-format), which means anyone is free to create an implementation for it. -The first one is made in [Rust](https://www.rust-lang.org/), primarily for [Substrate](https://substrate.io/)-based chains in the [Polkadot](https://polkadot.network/) ecosystem. -We'll be looking at this first implementation to tinker with different types of messages in the next sections. -For now, we'll take a look at how it's structured. +XCM is a [format](https://github.com/paritytech/xcm-format). +Anyone can create an implementation of the XCVM to interpret said format. + +Parity Technologies maintains a Rust implementation, primarily for [Substrate](https://substrate.io/)-based chains in the [Polkadot](https://polkadot.network/) ecosystem. +It is this implementation that we use throughout this documentation. All the code lives in the [Polkadot repo](https://github.com/paritytech/polkadot/tree/master/xcm). The main structure is as follows: -- XCM: Defines the fundamental constructs used in XCM and an enum with all the instructions available. -- Executor: Implements the XCVM, capable of executing XCMs. Highly configurable. -- Builder: Offers common configuration building blocks for the executor. -- Pallet: FRAME pallet that provides extrinsics with specific XCM programs. -- Simulator: Allows for testing of XCM programs. +- [XCM](https://github.com/paritytech/polkadot/tree/master/xcm/src): Defines the fundamental constructs used in XCM and an enum with all the instructions available. +- [Executor](https://github.com/paritytech/polkadot/tree/master/xcm/xcm-executor/src): Implements the XCVM, capable of executing XCMs. Highly configurable. +- [Builder](https://github.com/paritytech/polkadot/tree/master/xcm/xcm-builder/src): Offers common configuration building blocks for the executor. +- [Pallet](https://github.com/paritytech/polkadot/tree/master/xcm/pallet-xcm/src): FRAME pallet that provides extrinsics for interacting with the XCM executor, as well as specific XCM programs, such as teleports and reserve asset transfers. +- [Simulator](https://github.com/paritytech/polkadot/tree/master/xcm/xcm-simulator/example/src): Allows for testing of XCM programs. ## Executor diff --git a/src/overview/format.md b/src/overview/format.md index 2d2ff27..fb7678c 100644 --- a/src/overview/format.md +++ b/src/overview/format.md @@ -3,17 +3,16 @@ It's essential to understand that XCM is a format, not a protocol. It describes how messages should be structured and contains instructions that convey on-chain actions that the message intends to perform. However, XCM does not dictate how messages are delivered. - -That responsibility falls on [transport layer protocols](https://wiki.polkadot.network/docs/learn-xcm-transport) such as XCMP (Cross Chain Message Passing) and VMP (Vertical Message Passing) in the Polkadot ecosystem, or any others to come, such as bridging protocols. +That responsibility falls on [transport layer protocols](https://wiki.polkadot.network/docs/learn-xcm-transport) such as XCMP (Cross Chain Message Passing) and VMP (Vertical Message Passing) in the Polkadot ecosystem, or bridging protocols. This separation of concerns is useful, since it allows us to think of the interactions we want to build between systems without having to think about how the messages involved are actually routed. -XCM is similar to how RESTful services use REST as an architectural style of development, where HTTP requests contain specific parameters to perform some action. -Similar to UDP, out of the box XCM is a "fire and forget" model, unless there is a separate XCM designed to be a response message which can be sent from the recipient to the sender. All error handling should also be done on the recipient side. - -XCM is not designed in a way where every system supporting the format is expected to be able to interpret any possible XCM. Practically speaking, one can imagine that some messages will not have reasonable interpretations under some systems or will be intentionally unsupported. +Not every system is expected to be able to interpret any possible XCM. +Some messages will not have reasonable interpretations under some systems or will be intentionally unsupported. +For example, some consensus systems won't deal with NFTs, and that's okay. +Instructions that relate to NFTs will have valid interpretations on some systems but not on others. -Furthermore, it's essential to realize that XCMs by themselves are not considered on-chain transactions: XCM describes how to change the state of the target consensus system, but the message by itself does not perform state changes. In short, XCM is a declarative language; the actual interpretation and behaviour of each instruction in an XCM is defined by target's XCVM implementation. +Furthermore, XCMs by themselves are not considered on-chain transactions: XCM describes how to change the state of the target consensus system, but the message by itself does not perform state changes. +XCM communicates intentions; the actual interpretation and behaviour of each instruction in an XCM is defined by target's XCVM implementation. -XCM is a language in which rich interactions between systems can be written. Both simple and more complex scenarios can be expressed, and developers are encouraged to design and implement diverse cross-consensus communication solutions. diff --git a/src/overview/interoperability.md b/src/overview/interoperability.md index 480e784..9d6c9a1 100644 --- a/src/overview/interoperability.md +++ b/src/overview/interoperability.md @@ -1,19 +1,20 @@ # Introduction -XCM is a messaging format, designed to enable seamless communication between different consensus systems. Examples of consensus systems are blockchains and smart contracts. -XCM comes from the [Polkadot](https://polkadot.network/) ecosystem, but is designed to be general enough to provide a common language for cross-consensus communication that can be used anywhere. - +XCM is a **language** for communicating **intentions** between **consensus systems**. +Concretely, XCM is a message format, it specifies how to craft messages that communicate intentions to other consensus systems. +Some examples of consensus systems are blockchains and smart contracts. +XCM comes from the [Polkadot](https://polkadot.network/) ecosystem, but is designed to be general enough to provide a common format for cross-consensus communication that can be used anywhere. Its goal is to let blockchain ecosystems thrive via specialization instead of generalization. If there's no interoperability, a chain is forced to host all services and support all functionalities on its own. With XCM, we are able to achieve an ecosystem-wide division of labour: a chain can specialize and focus on its own business logic, and leverage the benefits of depending on other specialized blockchain for services that it does not provide. -XCM has four high-level inherent design assumptions: -1. Asynchronous: XCMs in no way assume that the sender will be blocking on its completion -2. Absolute: XCMs are assumed to be delivered and interpreted accurately, in order and in a timely fashion. Once a message is sent, one can assume that it will be processed as intended. -3. Asymmetric: XCMs, by default, do not have results that let the sender know that the message was received - they follow the 'fire and forget' paradigm. Any results must be separately communicated to the sender with an additional message back to the origin. -4. Agnostic: XCM makes no assumptions about the nature of the consensus systems between which the messages are being passed. XCM as a message format should be usable in any system that derives finality through consensus. +XCM makes the following assumptions regarding the underlying environment: +1. Asynchronous: XCMs in no way assume that the sender will be blocking on its completion. +2. Absolute: XCMs are assumed to be delivered and interpreted accurately, in order and in a timely fashion. Once a message is sent, one can assume that it will be processed as intended. This guarantee has to be provided by the transport layer. +3. Asymmetric: XCMs, by default, do not have results that let the sender know that the message was executed correctly. If results are needed, a new message must be sent. +4. Agnostic: XCM makes no assumptions about the nature of the consensus systems between which the messages are being passed. XCM should be usable in any system that derives finality through consensus. -XCM is a work-in-progress; the format is expected to change over time. +XCM is constantly evolving; the format is expected to change over time. It has an RFC process to propose changes, which end up in newer versions, the current one being v3. To keep up with the development of the format, or to propose changes, go to [the XCM format repository](https://github.com/paritytech/xcm-format). diff --git a/src/overview/xcvm.md b/src/overview/xcvm.md index 520a165..60a47fb 100644 --- a/src/overview/xcvm.md +++ b/src/overview/xcvm.md @@ -7,20 +7,20 @@ During execution, state is tracked in domain-specific registers, and is constant Most of the XCM format comprises these registers and the instructions used to compose XCVM programs. Like XCM, the XCVM is also a specification. -The implementation that will be used in this documentation is the [xcm-executor](https://github.com/paritytech/polkadot/tree/master/xcm/xcm-executor), built in Rust, provided by Parity. -It's built to be highly configurable, with its building blocks available in [xcm-builder](https://github.com/paritytech/polkadot/tree/master/xcm/xcm-builder). -Configuring the executor is an important and extensive topic, one we will dive into further in the [Config Deep Dive](../executor_config/index.md) chapter. +The implementation that will be used in this documentation is the [xcm-executor](https://github.com/paritytech/polkadot/tree/master/xcm/xcm-executor), provided by Parity. +The executor is highly configurable. +For more information on the extensive configuration options available, see the [Config Deep Dive](../executor_config/index.md) chapter. -Anyone is free to make an implementation of the XCVM. +Anyone can create an implementation of the XCVM. As long as they follow the standard, they'll be able to send XCMs to systems using other implementations. -Implementations in different programming languages will need to be used to bring XCM to other ecosystems. Typically, an XCM takes the following path through the XCVM: -- Instructions within an XCM are read one-by-one by the XCVM. An XCM may contain one or more instructions. +- Instructions within an XCM are read one-by-one. - The instruction is executed. This means that the current values of the XCVM registers, the instruction type, and the instruction operands are all used to execute some operation, which might result in some registers changing their value, or in an error being thrown, which would halt execution. - Each subsequent instruction within the XCM is read until the end of the message has been reached. -The XCVM register you will hear most about is the `holding` register. -An XCVM program that handles assets (which means most of them) will be putting them in and taking them out of this register. -Instructions we'll see later like `DepositAsset`, `WithdrawAsset` and many more, make use of this register. -You can see all registers in the [All XCVM Registers](../reference/xcvm-registers.md) section. +An example of an XCVM register is the holding register. +Any XCVM program that handles assets will be putting them in and taking them from this register. +This register is used by several of the instructions we will look at later, including `DepositAsset` and `WithdrawAsset`. + +For more information on other registers, see the [All XCVM Registers](../reference/xcvm-registers.md) section. diff --git a/src/xcm.md b/src/xcm.md index c32eee3..fd2f799 100644 --- a/src/xcm.md +++ b/src/xcm.md @@ -1,10 +1,23 @@ # XCM: Cross-Consensus Messaging -Welcome to the XCM documentation! +Welcome to the Cross-Consensus Messaging (XCM) documentation! +XCM is a **language** for communicating **intentions** between **consensus systems**. Whether you're a developer, a blockchain enthusiast, or just interested in Polkadot, this guide aims to provide you with an easy-to-understand and comprehensive introduction to XCM. -## Still under development +## Getting started -Keep in mind this documentation is still a work in progress. -We'll be polishing it and adding more content. -If there's anything in particular you'd like to see, or any pressing concern, please [open an issue](https://github.com/paritytech/xcm-docs/issues). +Head over to the [overview](overview/README.md) to begin your journey with XCM. + +## Configuration + +Head over to the [configuration section](executor_config/README.md) if you want to learn how to configure your project to use XCM. + +## Glossary + +Go to the [glossary](reference/glossary.md) section for a quick explanation of all the terms used when dealing with XCM. + +## Contribute + +Both the [format](https://github.com/paritytech/xcm-format) and this [documentation](https://github.com/paritytech/xcm-docs) are open for anyone to contribute. +If there's anything you'd like to see in the documentation, feel free to [open an issue](https://github.com/paritytech/xcm-docs/issues). +If you want to contribute to the format, check out the [RFC process](https://github.com/paritytech/xcm-format/blob/master/proposals/0001-process.md). From 693904029fe4df7ace7122abeacbc6077f3c24ef Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Thu, 24 Aug 2023 15:24:37 +0200 Subject: [PATCH 71/73] update origins test + add execution check function --- examples/src/4_origins/mod.rs | 13 +++++++--- examples/src/simple_test_net/mod.rs | 37 +++++++++++++++++++---------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/examples/src/4_origins/mod.rs b/examples/src/4_origins/mod.rs index 97a775f..31e0e48 100644 --- a/examples/src/4_origins/mod.rs +++ b/examples/src/4_origins/mod.rs @@ -5,7 +5,6 @@ mod tests { use xcm::latest::prelude::*; use xcm_simulator::TestExt; - const AMOUNT: u128 = 10; const QUERY_ID: u64 = 1234; /// Scenario: @@ -13,9 +12,13 @@ mod tests { fn descend_origin() { MockNet::reset(); ParaA::execute_with(|| { + let message_fee = parachain::estimate_message_fee(6); let message = Xcm(vec![ - WithdrawAsset((Here, AMOUNT).into()), - BuyExecution { fees: (Here, AMOUNT).into(), weight_limit: WeightLimit::Unlimited }, + WithdrawAsset((Here, message_fee).into()), + BuyExecution { + fees: (Here, message_fee).into(), + weight_limit: WeightLimit::Unlimited, + }, // Set the instructions that are executed when ExpectOrigin does not pass. // In this case, reporting back an error to the Parachain. SetErrorHandler(Xcm(vec![ReportError(QueryResponseInfo { @@ -30,6 +33,10 @@ mod tests { assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); }); + Relay::execute_with(|| { + assert!(relay_successful_execution()); + }); + // Check that message queue is empty. // The ExpectOrigin instruction passed so we should not receive an error response. ParaA::execute_with(|| assert_eq!(parachain::MsgQueue::received_dmp(), vec![])); diff --git a/examples/src/simple_test_net/mod.rs b/examples/src/simple_test_net/mod.rs index 917f21a..87539a1 100644 --- a/examples/src/simple_test_net/mod.rs +++ b/examples/src/simple_test_net/mod.rs @@ -138,17 +138,21 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { }; pallet_balances::GenesisConfig:: { - balances: vec![(ALICE, INITIAL_BALANCE), (relay_sovereign_account_id(), INITIAL_BALANCE), (BOB, INITIAL_BALANCE)] - .into_iter() - .chain(other_para_ids.iter().map( - // Initial balance of native token for ALICE on all sibling sovereign accounts - |¶_id| (sibling_account_sovereign_account_id(para_id, ALICE), INITIAL_BALANCE), - )) - .chain(other_para_ids.iter().map( - // Initial balance of native token all sibling sovereign accounts - |¶_id| (sibling_sovereign_account_id(para_id), INITIAL_BALANCE), - )) - .collect(), + balances: vec![ + (ALICE, INITIAL_BALANCE), + (relay_sovereign_account_id(), INITIAL_BALANCE), + (BOB, INITIAL_BALANCE), + ] + .into_iter() + .chain(other_para_ids.iter().map( + // Initial balance of native token for ALICE on all sibling sovereign accounts + |¶_id| (sibling_account_sovereign_account_id(para_id, ALICE), INITIAL_BALANCE), + )) + .chain(other_para_ids.iter().map( + // Initial balance of native token all sibling sovereign accounts + |¶_id| (sibling_sovereign_account_id(para_id), INITIAL_BALANCE), + )) + .collect(), } .assimilate_storage(&mut t) .unwrap(); @@ -180,7 +184,6 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { .assimilate_storage(&mut t) .unwrap(); - let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| { sp_tracing::try_init_simple(); @@ -226,6 +229,16 @@ pub fn print_relay_events() { System::events().iter().for_each(|r| println!(">>> {:?}", r.event)); } +pub fn relay_successful_execution() -> bool { + use relay_chain::System; + System::events().iter().any(|e| match &e.event { + relay_chain::RuntimeEvent::ParasUmp( + polkadot_runtime_parachains::ump::Event::ExecutedUpward(_, outcome), + ) => outcome.clone().ensure_complete().is_ok(), + _ => false, + }) +} + pub type RelaychainPalletXcm = pallet_xcm::Pallet; pub type ParachainPalletXcm = pallet_xcm::Pallet; pub type RelaychainBalances = pallet_balances::Pallet; From 0e4a5fb58b9421288f4fc5e6def9c5d8b6b3f065 Mon Sep 17 00:00:00 2001 From: Just van Stam Date: Fri, 1 Sep 2023 17:46:41 +0200 Subject: [PATCH 72/73] fix (#44) --- examples/src/simple_test_net/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/src/simple_test_net/mod.rs b/examples/src/simple_test_net/mod.rs index 60d1b30..7089dfa 100644 --- a/examples/src/simple_test_net/mod.rs +++ b/examples/src/simple_test_net/mod.rs @@ -237,9 +237,9 @@ pub fn print_relay_events() { pub fn relay_successful_execution() -> bool { use relay_chain::System; System::events().iter().any(|e| match &e.event { - relay_chain::RuntimeEvent::ParasUmp( - polkadot_runtime_parachains::ump::Event::ExecutedUpward(_, outcome), - ) => outcome.clone().ensure_complete().is_ok(), + relay_chain::RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed{id: _, origin: _, weight_used: _, success: true}, + ) => true, _ => false, }) } From c86f214b5bc47e100d5b85cc436c6a6a453c81f8 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 29 May 2024 20:12:52 +0100 Subject: [PATCH 73/73] doc: mark as deprecated, point to new docs (#50) --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5700e08..eea99bb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ -# xcm-docs -Documentation for XCM +# XCM Docs - DEPRECATED + +The documentation for XCM has moved to [polkadot-sdk](https://github.com/paritytech/polkadot-sdk/) rust docs. +You can find an online rendered version [here](https://paritytech.github.io/polkadot-sdk/master/xcm_docs/index.html).